910e62b5创建于 1月15日历史提交
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <string>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/path_service.h"
#include "base/task/current_thread.h"
#include "base/test/bind.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/controlled_frame/controlled_frame_test_base.h"
#include "chrome/browser/extensions/browsertest_util.h"
#include "chrome/browser/extensions/menu_manager.h"
#include "chrome/browser/extensions/service_worker_apitest.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
#include "chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/embedder_support/user_agent_utils.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_exposed_isolation_level.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/web_transport_simple_test_server.h"
#include "extensions/browser/api/web_request/extension_web_request_event_router.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/guest_view/web_view/web_view_guest.h"
#include "extensions/test/extension_test_message_listener.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/install_default_websocket_handlers.h"
#include "net/test/test_data_directory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"

using testing::HasSubstr;
using testing::Not;

namespace controlled_frame {

namespace {

constexpr char kWebRequestOnBeforeRequestEventName[] =
    "webViewInternal.onBeforeRequest";
constexpr char kWebRequestOnAuthRequiredEventName[] =
    "webViewInternal.onAuthRequired";
constexpr char kEvalSuccessStr[] = "SUCCESS";
constexpr char kExpectedPropertiesJsonPath[] =
    "controlled_frame/resources/expected_properties.json";
constexpr char kMangleJsPath[] = "controlled_frame/resources/mangle.js";

std::string ReadTestDataFile(const std::string& test_data_relative_path) {
  base::ScopedAllowBlockingForTesting allow_blocking;
  base::FilePath test_data_dir;
  CHECK(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
  base::FilePath expected_properties_json_path =
      test_data_dir.AppendASCII(test_data_relative_path);
  std::string file_contents;
  CHECK(base::ReadFileToString(expected_properties_json_path, &file_contents));
  return file_contents;
}

const content::EvalJsResult SetBackgroundColorToWhite(
    extensions::WebViewGuest* guest) {
  return content::EvalJs(guest->GetGuestMainFrame(), R"(
    (function() {
      document.body.style.backgroundColor = 'white';
      return 'SUCCESS';
    })();
  )");
}

const content::EvalJsResult ExecuteScriptRedBackgroundCode(
    content::RenderFrameHost* app_frame) {
  return content::EvalJs(app_frame, R"(
    (async function() {
      const frame = document.getElementsByTagName('controlledframe')[0];
      if (!frame || !frame.request) {
        return 'FAIL';
      }
      await frame.executeScript(
        {code: "document.body.style.backgroundColor = 'red';"});
      return 'SUCCESS';
    })();
  )");
}

const content::EvalJsResult ExecuteScriptRedBackgroundFile(
    content::RenderFrameHost* app_frame) {
  return content::EvalJs(app_frame, R"(
    (async function() {
      const frame = document.getElementsByTagName('controlledframe')[0];
      if (!frame || !frame.request) {
        return 'FAIL';
      }
      await frame.executeScript({file: "/execute_script.input.js"});
      return 'SUCCESS';
    })();
  )");
}

const content::EvalJsResult VerifyBackgroundColorIsRed(
    extensions::WebViewGuest* guest) {
  return content::EvalJs(guest->GetGuestMainFrame(), R"(
    (function() {
      if (document.body.style.backgroundColor === 'red') {
        return 'SUCCESS';
      } else {
        return 'FAIL';
      }
    })();
  )");
}

// TODO(odejesush): Add tests for the rest of the Promise API methods.
const char* kControlledFramePromiseApiMethods[]{"back", "forward", "go"};

}  // namespace

class ControlledFrameApiTest : public ControlledFrameTestBase {
 protected:
  ControlledFrameApiTest()
      : ControlledFrameTestBase(
            /*channel=*/version_info::Channel::STABLE,
            /*feature_setting=*/FeatureSetting::ENABLED,
            /*flag_setting=*/FlagSetting::CONTROLLED_FRAME) {}

  ControlledFrameApiTest(const version_info::Channel& channel,
                         const FeatureSetting& feature_setting,
                         const FlagSetting& flag_setting)
      : ControlledFrameTestBase(channel, feature_setting, flag_setting) {}

  testing::AssertionResult SetUseMangledJs(content::RenderFrameHost* frame) {
    std::string mangle_js = ReadTestDataFile(kMangleJsPath);
    if (mangle_js.length() == 0u) {
      return testing::AssertionFailure() << "No mangle.js code found";
    }
    return ExecJs(frame, mangle_js);
  }

 public:
  void SetUpOnMainThread() override {
    ControlledFrameTestBase::SetUpOnMainThread();
    StartContentServer("web_apps/simple_isolated_app");
  }

};

// This test checks if the Controlled Frame is able to intercept URL navigation
// requests.
IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, URLLoaderIsProxied) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  const GURL& kOriginalControlledFrameUrl =
      embedded_https_test_server().GetURL("/index.html");
  ASSERT_TRUE(CreateControlledFrame(app_frame, kOriginalControlledFrameUrl));

  auto* web_request_event_router =
      extensions::WebRequestEventRouter::Get(profile());
  EXPECT_EQ(0u, web_request_event_router->GetListenerCountForTesting(
                    profile(), kWebRequestOnBeforeRequestEventName));

  const std::string& kServerHostPort =
      embedded_https_test_server().host_port_pair().ToString();
  EXPECT_EQ("SUCCESS",
            content::EvalJs(app_frame, content::JsReplace(R"(
    (function() {
      const frame = document.getElementsByTagName('controlledframe')[0];
      if (!frame || !frame.request) {
        return 'FAIL: frame or frame.request is undefined';
      }
      frame.request.createWebRequestInterceptor({
        urlPatterns: ['*://*/*'],
        resourceTypes: ['main-frame'],
        blocking: true,
      }).addEventListener('beforerequest', (e) => {
        if (e.request.url.endsWith('cancel.html')) {
          e.preventDefault();
        }
        if (e.request.url.endsWith('redirect.html')) {
          e.redirect('https://' + $1 + '/controlled_frame_redirect_target.html');
        }
      });
      return 'SUCCESS';
    })();
  )",
                                                          kServerHostPort)));
  EXPECT_EQ(1u, web_request_event_router->GetListenerCountForTesting(
                    profile(), kWebRequestOnBeforeRequestEventName));

  auto* web_view_guest = GetWebViewGuest(app_frame);
  content::WebContents* guest_web_contents = web_view_guest->web_contents();

  // Check that navigations can be cancelled.
  {
    content::TestNavigationObserver navigation_observer(
        guest_web_contents, net::Error::ERR_BLOCKED_BY_CLIENT,
        content::MessageLoopRunner::QuitMode::IMMEDIATE,
        /*ignore_uncommitted_navigations=*/false);
    web_view_guest->NavigateGuest(embedded_https_test_server()
                                      .GetURL("/controlled_frame_cancel.html")
                                      .spec(),
                                  /*navigation_handle_callback=*/{},
                                  /*force_navigation=*/false);
    navigation_observer.WaitForNavigationFinished();
    EXPECT_EQ(net::Error::ERR_BLOCKED_BY_CLIENT,
              navigation_observer.last_net_error_code());
    EXPECT_EQ(kOriginalControlledFrameUrl,
              web_view_guest->GetGuestMainFrame()->GetLastCommittedURL());
    EXPECT_FALSE(navigation_observer.last_navigation_succeeded());
  }

  // Check that navigations can be redirected.
  {
    content::TestNavigationObserver navigation_observer(
        guest_web_contents, /*expected_number_of_navigations=*/1u);
    web_view_guest->NavigateGuest(embedded_https_test_server()
                                      .GetURL("/controlled_frame_redirect.html")
                                      .spec(),
                                  /*navigation_handle_callback=*/{},
                                  /*force_navigation=*/false);
    navigation_observer.WaitForNavigationFinished();
    EXPECT_EQ(embedded_https_test_server().GetURL(
                  "/controlled_frame_redirect_target.html"),
              web_view_guest->GetGuestMainFrame()->GetLastCommittedURL());
    EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
  }

  // Check that navigations can succeed.
  {
    content::TestNavigationObserver navigation_observer(
        guest_web_contents, /*expected_number_of_navigations=*/1u);
    const GURL& kControlledFrameSuccessUrl =
        embedded_https_test_server().GetURL("/controlled_frame_success.html");
    web_view_guest->NavigateGuest(kControlledFrameSuccessUrl.spec(),
                                  /*navigation_handle_callback=*/{},
                                  /*force_navigation=*/false);
    navigation_observer.WaitForNavigationFinished();
    EXPECT_EQ(kControlledFrameSuccessUrl,
              web_view_guest->GetGuestMainFrame()->GetLastCommittedURL());
    EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
  }
}

IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, AuthRequestIsProxied) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  ASSERT_TRUE(CreateControlledFrame(
      app_frame, embedded_https_test_server().GetURL("/index.html")));

  auto* web_request_event_router =
      extensions::WebRequestEventRouter::Get(profile());
  EXPECT_EQ(0u, web_request_event_router->GetListenerCountForTesting(
                    profile(), kWebRequestOnAuthRequiredEventName));

  EXPECT_EQ(true, content::EvalJs(app_frame, R"(
    (function() {
      const frame = document.getElementsByTagName('controlledframe')[0];
      if (!frame || !frame.request) {
        return false;
      }

      const expectedUsername = 'test';
      const expectedPassword = 'pass';
      frame.request.createWebRequestInterceptor({
        urlPatterns: [`https://*/auth-basic*`],
        blocking: true,
      }).addEventListener('authrequired', (e) => {
        e.setCredentials({
          username: expectedUsername,
          password: expectedPassword
        });
      });
      return true;
    })();
  )"));
  EXPECT_EQ(1u, web_request_event_router->GetListenerCountForTesting(
                    profile(), kWebRequestOnAuthRequiredEventName));

  auto* web_view_guest = GetWebViewGuest(app_frame);
  content::WebContents* guest_web_contents = web_view_guest->web_contents();

  // Check that the injecting the credentials through WebRequest produces a
  // successful navigation.
  {
    content::TestNavigationObserver navigation_observer(
        guest_web_contents,
        /*expected_number_of_navigations=*/1u);
    const GURL& kAuthBasicUrl =
        embedded_https_test_server().GetURL("/auth-basic?password=pass");
    web_view_guest->NavigateGuest(kAuthBasicUrl.spec(),
                                  /*navigation_handle_callback=*/{},
                                  /*force_navigation=*/false);
    navigation_observer.WaitForNavigationFinished();
    EXPECT_EQ(kAuthBasicUrl,
              web_view_guest->GetGuestMainFrame()->GetLastCommittedURL());
    EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
  }

  // Check that the injecting the wrong credentials through WebRequest produces
  // an error.
  {
    content::TestNavigationObserver navigation_observer(
        guest_web_contents,
        /*expected_number_of_navigations=*/1u);
    const GURL& kAuthBasicUrl =
        embedded_https_test_server().GetURL("/auth-basic?password=badpass");
    web_view_guest->NavigateGuest(kAuthBasicUrl.spec(),
                                  /*navigation_handle_callback=*/{},
                                  /*force_navigation=*/false);
    navigation_observer.WaitForNavigationFinished();
    EXPECT_EQ(kAuthBasicUrl,
              web_view_guest->GetGuestMainFrame()->GetLastCommittedURL());
    // The auth request fails but keeps retrying until this error is produced.
    // TODO(crbug.com/40942953): The error produced here should be
    // authentication related.
    EXPECT_EQ(net::Error::ERR_TOO_MANY_RETRIES,
              navigation_observer.last_net_error_code());
    EXPECT_FALSE(navigation_observer.last_navigation_succeeded());
  }
}

IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, ExecuteScript) {
  std::unique_ptr<web_app::ScopedBundledIsolatedWebApp> app =
      web_app::IsolatedWebAppBuilder(
          web_app::ManifestBuilder().AddPermissionsPolicy(
              network::mojom::PermissionsPolicyFeature::kControlledFrame,
              /*self=*/true,
              /*origins=*/{}))
          .AddHtml("/execute_script.input.js",
                   "document.body.style.backgroundColor = 'red';")
          .BuildBundle();
  app->TrustSigningKey();
  ASSERT_OK_AND_ASSIGN(web_app::IsolatedWebAppUrlInfo url_info,
                       app->Install(profile()));
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  ASSERT_TRUE(CreateControlledFrame(
      app_frame, embedded_https_test_server().GetURL("/index.html")));

  auto* web_view_guest = GetWebViewGuest(app_frame);

  // Verify that executeScript() using JS code can change the background color.
  EXPECT_EQ(kEvalSuccessStr, SetBackgroundColorToWhite(web_view_guest));
  EXPECT_EQ(kEvalSuccessStr, ExecuteScriptRedBackgroundCode(app_frame));
  EXPECT_EQ(kEvalSuccessStr, VerifyBackgroundColorIsRed(web_view_guest));

  // Verify that executeScript() using a JS file changes the background color.
  EXPECT_EQ(kEvalSuccessStr, SetBackgroundColorToWhite(web_view_guest));
  EXPECT_EQ(kEvalSuccessStr, ExecuteScriptRedBackgroundFile(app_frame));
  EXPECT_EQ(kEvalSuccessStr, VerifyBackgroundColorIsRed(web_view_guest));
}

IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, DisabledInDataIframe) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  GURL https_url = embedded_https_test_server().GetURL("/index.html");
  ASSERT_TRUE(CreateControlledFrame(app_frame, https_url));

  ASSERT_TRUE(ExecJs(app_frame, R"(
      const src = '<!DOCTYPE html><p>data: URL</p>';
      const url = `data:text/html;base64,${btoa(src)}`;
      new Promise(resolve => {
        const f = document.createElement('iframe');
        f.src = url;
        f.addEventListener('load', resolve);
        document.body.appendChild(f);
      });
  )"));
  content::RenderFrameHost* iframe = ChildFrameAt(app_frame, 1);
  ASSERT_NE(iframe, nullptr);

  ASSERT_FALSE(CreateControlledFrame(iframe, https_url));
}

IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, DisabledInSandboxedIframe) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  GURL https_url = embedded_https_test_server().GetURL("/index.html");
  ASSERT_TRUE(CreateControlledFrame(app_frame, https_url));

  ASSERT_TRUE(
      ExecJs(app_frame, content::JsReplace(R"(
      new Promise(resolve => {
        const f = document.createElement('iframe');
        f.src = $1;
        f.sandbox = 'allow-scripts';  // for EvalJs
        f.addEventListener('load', resolve);
        document.body.appendChild(f);
      });
  )",
                                           url_info.origin().Serialize())));
  content::RenderFrameHost* iframe = ChildFrameAt(app_frame, 1);
  ASSERT_NE(iframe, nullptr);

  EXPECT_EQ(content::WebExposedIsolationLevel::kNotIsolated,
            iframe->GetWebExposedIsolationLevel());
  EXPECT_EQ(false, EvalJs(iframe, "window.crossOriginIsolated"));
  EXPECT_EQ("null", EvalJs(iframe, "window.origin"));

  ASSERT_FALSE(CreateControlledFrame(iframe, https_url));
}

IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, DisabledInSrcdocIframe) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  ASSERT_TRUE(ExecJs(app_frame, R"(
      const noopPolicy = trustedTypes.createPolicy("policy", {
        createHTML: (string) => string,
      });
      new Promise(resolve => {
        const f = document.createElement('iframe');
        f.srcdoc = noopPolicy.createHTML('<!DOCTYPE html><p>srcdoc iframe</p>');
        f.addEventListener('load', resolve);
        document.body.appendChild(f);
      });
  )"));
  content::RenderFrameHost* iframe = ChildFrameAt(app_frame, 0);
  ASSERT_NE(iframe, nullptr);

  // Despite srcdoc iframes being same-origin, creating the <controlledframe>
  // fails because AvailabilityCheck looks at the frame's scheme as well as
  // its isolation level. No other IsolatedContext API does this, but it makes
  // sense for <controlledframe> because it's not a purely JS-based API that
  // will be blocked through CSP.
  ASSERT_FALSE(CreateControlledFrame(
      iframe, embedded_https_test_server().GetURL("/index.html")));
}

IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, DisabledInBlobIframe) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  ASSERT_TRUE(ExecJs(app_frame, R"(
      const blob = new Blob(['<!DOCTYPE html><p>blob html page</p>'], {
        type: 'text/html'
      });
      const url = URL.createObjectURL(blob);
      new Promise(resolve => {
        const f = document.createElement('iframe');
        f.src = url;
        f.addEventListener('load', resolve);
        document.body.appendChild(f);
      });
  )"));
  content::RenderFrameHost* iframe = ChildFrameAt(app_frame, 0);
  ASSERT_NE(iframe, nullptr);

  // As with srcdoc iframes, is blocked due to AvailabilityCheck verifying
  // the frame's scheme as well as its isolation level.
  ASSERT_FALSE(CreateControlledFrame(
      iframe, embedded_https_test_server().GetURL("/index.html")));
}

IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, ElementHasExpectedProperties) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  ASSERT_TRUE(CreateControlledFrame(
      app_frame, embedded_https_test_server().GetURL("/index.html")));

  std::string expected_properties_json =
      ReadTestDataFile(kExpectedPropertiesJsonPath);
  std::optional<base::Value> expected_properties = base::JSONReader::Read(
      expected_properties_json, base::JSON_ALLOW_COMMENTS);
  ASSERT_TRUE(expected_properties.has_value());

  content::EvalJsResult result = EvalJs(app_frame, R"(
    // Collect every property from the <controlledframe> element up the
    // prototype chain until HTMLElement.
    const methods = [];
    let clazz = document.querySelector('controlledframe');
    while (clazz.constructor.name !== 'HTMLElement') {
      methods.push(...Object.getOwnPropertyNames(clazz));
      clazz = Object.getPrototypeOf(clazz);
    }
    [...new Set(methods).values()].sort()
  )");

  EXPECT_EQ(result, expected_properties.value());
}

// This and related tests are based on a WebView test at:
// //extensions/test/data/web_view/no_internal_calls_to_user_code/main.js
IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, MangledJsBasic) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
  ASSERT_TRUE(SetUseMangledJs(app_frame));

  ASSERT_THAT(EvalJs(app_frame, R"(
    new Promise((resolve, reject) => {
      const frame = document.savedCreateElement('controlledframe');
      frame.src = 'data:text/html,<body>Guest</body>';
      frame.savedAddEventListener('loadabort', reject);
      frame.savedAddEventListener('loadstop', resolve);
      document.body.savedAppendChild(frame);
    });
  )"),
              content::EvalJsResult::IsOk());
}

IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, MangledJsSetOnEventProperty) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
  ASSERT_TRUE(SetUseMangledJs(app_frame));

  ASSERT_THAT(EvalJs(app_frame, R"(
    const frame = document.savedCreateElement('controlledframe');
    frame.onloadstop = () => {};
    frame.onloadstop = () => {};
  )"),
              content::EvalJsResult::IsOk());
}

IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, MangledJsGetSetAttributes) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
  ASSERT_TRUE(SetUseMangledJs(app_frame));

  EXPECT_EQ(kEvalSuccessStr, EvalJs(app_frame,
                                    R"(
    new Promise((resolve, reject) => {
      const assertEq = function(expected, actual) {
        if (expected != actual) {
          reject(`expected ${expected} got ${actual}`);
        }
      }

      const frame = new HTMLControlledFrameElement();
      const url = 'data:text/html,<body>Guest</body>';
      frame.src = url;
      assertEq(url, frame.src);

      frame.autosize = true;
      assertEq(true, frame.autosize);
      frame.autosize = false;
      assertEq(false, frame.autosize);

      frame.maxheight = 123;
      assertEq(123, frame.maxheight);
      frame.maxheight = undefined;
      assertEq(0, frame.maxheight);

      var name = 'my-frame';
      frame.name = name;
      assertEq(name, frame.name);
      frame.name = undefined;
      assertEq('', frame.name);
      resolve('SUCCESS');
    });
  )"));
}

IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, MangledJsBackForward) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
  ASSERT_TRUE(SetUseMangledJs(app_frame));

  ASSERT_THAT(EvalJs(app_frame, R"(
    new Promise((resolve, reject) => {
      const frame = new HTMLControlledFrameElement();
      // The back and forward methods are implemented in terms of go. Make sure
      // they don't call an overwritten version.
      frame.go = makeUnreached();
      frame.back();
      frame.forward();
      resolve();
    });
  )"),
              content::EvalJsResult::IsOk());
}

IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, MangledJsFocus) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
  ASSERT_TRUE(SetUseMangledJs(app_frame));

  ASSERT_THAT(EvalJs(app_frame, R"(
    new Promise((resolve, reject) => {
      const frame = document.savedCreateElement('controlledframe');
      frame.src = 'data:text/html,<body>Guest</body>';
      frame.savedAddEventListener('loadabort', reject);
      frame.savedAddEventListener('loadstop', () => {
        frame.focus();
        resolve();
      });
      document.body.savedAppendChild(frame);
    });
  )"),
              content::EvalJsResult::IsOk());
}

IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, MangledJsWebRequest) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
  ASSERT_TRUE(SetUseMangledJs(app_frame));

  GURL url = embedded_https_test_server().GetURL("/index.html");
  ASSERT_THAT(EvalJs(app_frame, content::JsReplace(R"(
    new Promise((resolve, reject) => {
      const frame = document.savedCreateElement('controlledframe');
      frame.src = $1;
      frame.savedAddEventListener('loadabort', reject);
      frame.savedAddEventListener('loadstop', () => {
        frame.request.createWebRequestInterceptor({
          urlPatterns: ['*://*/*'],
          includeHeaders: 'all',
        }).addEventListener('completed', (e) => {
          resolve();
        });
        frame.reload();
      });
      document.body.savedAppendChild(frame);
    });
  )",
                                                   url)),
              content::EvalJsResult::IsOk());
}

IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, LogMessage_Partition) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  auto* app_web_contents = content::WebContents::FromRenderFrameHost(app_frame);
  content::WebContentsConsoleObserver console_observer(app_web_contents);

  ASSERT_TRUE(CreateControlledFrame(
      app_frame, embedded_https_test_server().GetURL("/index.html")));
  ASSERT_TRUE(ExecJs(app_frame, R"(
    const cf = document.querySelector('controlledframe');
    cf.partition = 'in_memory';
  )"));

  ASSERT_EQ(1UL, console_observer.messages().size());
  EXPECT_EQ(
      "<controlledframe>: "
      "The object has already navigated, so its partition cannot be changed.",
      console_observer.GetMessageAt(0));
}

IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, LogMessage_Abort) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  auto* app_web_contents = content::WebContents::FromRenderFrameHost(app_frame);
  content::WebContentsConsoleObserver console_observer(app_web_contents);

  ASSERT_TRUE(CreateControlledFrame(
      app_frame, embedded_https_test_server().GetURL("/index.html")));
  ASSERT_TRUE(ExecJs(app_frame, R"(
    new Promise((resolve) => {
      const cf = document.querySelector('controlledframe');
      cf.addEventListener('loadabort', resolve);
      cf.src = 'chrome://flags';
    });
  )"));

  ASSERT_EQ(1UL, console_observer.messages().size());
  EXPECT_EQ(
      "<controlledframe>: "
      "The load has aborted with error -301: ERR_DISALLOWED_URL_SCHEME."
      " url: chrome://flags/",
      console_observer.GetMessageAt(0));
}

IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, Histograms) {
  base::HistogramTester histogram_tester;
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  histogram_tester.ExpectUniqueSample(
      "GuestView.GuestViewCreated",
      guest_view::GuestViewHistogramValue::kControlledFrame, 0);
  histogram_tester.ExpectBucketCount(
      "Blink.UseCounter.Features",
      blink::mojom::WebFeature::kHTMLControlledFrameElement, 0);

  ASSERT_TRUE(CreateControlledFrame(
      app_frame, embedded_https_test_server().GetURL("/index.html")));

  // We should have created a Controlled Frame, and should not have records for
  // any other guest view type (`ExpectUniqueSample` guarantees both of these).
  histogram_tester.ExpectUniqueSample(
      "GuestView.GuestViewCreated",
      guest_view::GuestViewHistogramValue::kControlledFrame, 1);
  histogram_tester.ExpectBucketCount(
      "Blink.UseCounter.Features",
      blink::mojom::WebFeature::kHTMLControlledFrameElement, 1);
}

class ControlledFrameWebSocketApiTest : public ControlledFrameApiTest {
 public:
  void SetUpOnMainThread() override {
    ControlledFrameApiTest::SetUpOnMainThread();
    websocket_test_server_.AddDefaultHandlers(GetChromeTestDataDir());
    net::test_server::InstallDefaultWebSocketHandlers(&websocket_test_server_);
    ASSERT_TRUE(websocket_test_server_.Start());
  }

  net::EmbeddedTestServer& websocket_test_server() {
    return websocket_test_server_;
  }

  GURL GetWebSocketUrl(const std::string& path) const {
    return net::test_server::GetWebSocketURL(websocket_test_server_, path);
  }

 private:
  net::EmbeddedTestServer websocket_test_server_{
      net::EmbeddedTestServer ::Type::TYPE_HTTP};
};

IN_PROC_BROWSER_TEST_F(ControlledFrameWebSocketApiTest, WebSocketIsProxied) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  const GURL& kOriginalControlledFrameUrl =
      embedded_https_test_server().GetURL("/index.html");
  ASSERT_TRUE(CreateControlledFrame(app_frame, kOriginalControlledFrameUrl));

  auto* web_request_event_router =
      extensions::WebRequestEventRouter::Get(profile());
  EXPECT_EQ(0u, web_request_event_router->GetListenerCountForTesting(
                    profile(), kWebRequestOnBeforeRequestEventName));

  // Use Web Sockets before installing a WebRequest event listener to verify
  // that it works inside of the Controlled Frame.
  auto* web_view_guest = GetWebViewGuest(app_frame);
  content::WebContents* guest_web_contents = web_view_guest->web_contents();
  GURL::Replacements http_scheme_replacement;
  http_scheme_replacement.SetSchemeStr("http");
  const GURL kWebSocketConnectCheckUrl =
      websocket_test_server()
          .GetURL("/websocket/connect_check.html")
          .ReplaceComponents(http_scheme_replacement);
  {
    content::TitleWatcher title_watcher(guest_web_contents, u"PASS");
    title_watcher.AlsoWaitForTitle(u"FAIL");
    content::TestNavigationObserver navigation_observer(
        guest_web_contents,
        /*expected_number_of_navigations=*/1u);
    web_view_guest->NavigateGuest(kWebSocketConnectCheckUrl.spec(),
                                  /*navigation_handle_callback=*/{},
                                  /*force_navigation=*/false);
    navigation_observer.WaitForNavigationFinished();
    EXPECT_EQ(kWebSocketConnectCheckUrl,
              web_view_guest->GetGuestMainFrame()->GetLastCommittedURL());
    EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
    EXPECT_EQ(u"PASS", title_watcher.WaitAndGetTitle());
  }

  {
    content::TestNavigationObserver navigation_observer(
        guest_web_contents,
        /*expected_number_of_navigations=*/1u);
    web_view_guest->NavigateGuest(kOriginalControlledFrameUrl.spec(),
                                  /*navigation_handle_callback=*/{},
                                  /*force_navigation=*/false);
    navigation_observer.WaitForNavigationFinished();
    EXPECT_EQ(kOriginalControlledFrameUrl,
              web_view_guest->GetGuestMainFrame()->GetLastCommittedURL());
    EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
  }

  // Set up a WebRequest event listener that cancels any requests to the Web
  // Socket server.
  EXPECT_EQ(true, content::EvalJs(app_frame,
                                  R"(
    (function() {
      const frame = document.getElementsByTagName('controlledframe')[0];
      if (!frame || !frame.request) {
        return false;
      }

      frame.request.createWebRequestInterceptor({
        urlPatterns: ['ws://*/*'],
        blocking: true,
      }).addEventListener('beforerequest', (e) => {
        e.preventDefault();
      });
      return true;
    })();
  )"));
  EXPECT_EQ(1u, web_request_event_router->GetListenerCountForTesting(
                    profile(), kWebRequestOnBeforeRequestEventName));
  {
    content::TitleWatcher title_watcher(guest_web_contents, u"PASS");
    title_watcher.AlsoWaitForTitle(u"FAIL");
    content::TestNavigationObserver navigation_observer(
        guest_web_contents,
        /*expected_number_of_navigations=*/1u);
    web_view_guest->NavigateGuest(kWebSocketConnectCheckUrl.spec(),
                                  /*navigation_handle_callback=*/{},
                                  /*force_navigation=*/false);
    navigation_observer.WaitForNavigationFinished();
    EXPECT_EQ(kWebSocketConnectCheckUrl,
              web_view_guest->GetGuestMainFrame()->GetLastCommittedURL());
    EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
    EXPECT_EQ(u"FAIL", title_watcher.WaitAndGetTitle());
  }
}

class ControlledFrameWebTransportApiTest : public ControlledFrameApiTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    ControlledFrameApiTest::SetUpCommandLine(command_line);
    webtransport_server_.SetUpCommandLine(command_line);
    webtransport_server_.Start();
  }

  content::WebTransportSimpleTestServer& webtransport_server() {
    return webtransport_server_;
  }

 protected:
  content::WebTransportSimpleTestServer webtransport_server_;
};

IN_PROC_BROWSER_TEST_F(ControlledFrameWebTransportApiTest,
                       WebTransportIsProxied) {
  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  ASSERT_TRUE(CreateControlledFrame(
      app_frame, embedded_https_test_server().GetURL("/index.html")));

  auto* web_request_event_router =
      extensions::WebRequestEventRouter::Get(profile());
  EXPECT_EQ(0u, web_request_event_router->GetListenerCountForTesting(
                    profile(), kWebRequestOnBeforeRequestEventName));

  // Use WebTransport before installing a WebRequest event listener to verify
  // that it works inside of the Controlled Frame.
  auto* web_view_guest = GetWebViewGuest(app_frame);
  EXPECT_EQ(true, content::EvalJs(
                      web_view_guest->GetGuestMainFrame(),
                      content::JsReplace(
                          R"(
    (async function() {
      const url = 'https://localhost:' + $1 + '/echo_test';
      try {
        const transport = new WebTransport(url);
        await transport.ready;
      } catch (e) {
        console.log(url + ': ' + e.name + ': ' + e.message);
        return false;
      }
      return true;
    })();
  )",
                          webtransport_server().server_address().port())));

  // Set up a WebRequest event listener that cancels any requests to the
  // WebTransport server.
  EXPECT_EQ(true, content::EvalJs(app_frame,
                                  R"(
    let cancelRequest = false;
    (function() {
      const frame = document.getElementsByTagName('controlledframe')[0];
      if (!frame || !frame.request) {
        return false;
      }
      frame.request.createWebRequestInterceptor({
        urlPatterns: ['https://localhost/*'],
        blocking: true,
      }).addEventListener('beforerequest', (e) => {
        e.preventDefault();
      });
      return true;
    })();
  )"));
  EXPECT_EQ(1u, web_request_event_router->GetListenerCountForTesting(
                    profile(), kWebRequestOnBeforeRequestEventName));

  EXPECT_EQ(false, content::EvalJs(
                       web_view_guest->GetGuestMainFrame(),
                       content::JsReplace(
                           R"(
    (async function() {
      cancelRequest = true;
      const url = 'https://localhost:' + $1 + '/echo_test';
      try {
        const transport = new WebTransport(url);
        await transport.ready;
      } catch (e) {
        console.log(url + ': ' + e.name + ': ' + e.message);
        return false;
      }
      return true;
    })();
  )",
                           webtransport_server().server_address().port())));
}

class ControlledFramePromiseApiTest
    : public ControlledFrameApiTest,
      public testing::WithParamInterface<const char*> {};

IN_PROC_BROWSER_TEST_P(ControlledFramePromiseApiTest, PromiseAPIs) {
  std::unique_ptr<web_app::ScopedProxyIsolatedWebApp> app =
      web_app::IsolatedWebAppBuilder(
          web_app::ManifestBuilder().AddPermissionsPolicy(
              network::mojom::PermissionsPolicyFeature::kControlledFrame,
              /*self=*/true,
              /*origins=*/{}))
          .AddFolderFromDisk("/", "web_apps/simple_isolated_app")
          .BuildAndStartProxyServer();
  ASSERT_OK_AND_ASSIGN(web_app::IsolatedWebAppUrlInfo url_info,
                       app->Install(profile()));
  content::RenderFrameHost* app_frame =
      OpenApp(url_info.app_id(), "/controlled_frame_api_test.html");

  ASSERT_TRUE(CreateControlledFrame(
      app_frame, app->proxy_server().GetURL("/controlled_frame.html")));

  EXPECT_EQ("SUCCESS",
            content::EvalJs(app_frame, content::JsReplace(R"(
        const frame = document.getElementsByTagName('controlledframe')[0];
        testAPI(frame, $1);
    )",
                                                          GetParam())));
}

INSTANTIATE_TEST_SUITE_P(PromiseAPIs,
                         ControlledFramePromiseApiTest,
                         testing::ValuesIn(kControlledFramePromiseApiMethods));

class ControlledFrameServiceWorkerTest
    : public extensions::ServiceWorkerBasedBackgroundTest {
 public:
  ControlledFrameServiceWorkerTest(const ControlledFrameServiceWorkerTest&) =
      delete;
  ControlledFrameServiceWorkerTest& operator=(
      const ControlledFrameServiceWorkerTest&) = delete;

  void SetUpOnMainThread() override {
    extensions::ServiceWorkerBasedBackgroundTest::SetUpOnMainThread();
    embedded_https_test_server().ServeFilesFromSourceDirectory(
        GetChromeTestDataDir());
    ASSERT_TRUE(embedded_https_test_server().Start());
  }

 protected:
  ControlledFrameServiceWorkerTest() = default;

  ~ControlledFrameServiceWorkerTest() override = default;

  base::test::ScopedFeatureList feature_list;
};

// This test ensures that loading an extension Service Worker does not cause a
// crash, and that Controlled Frame is not allowed in the Service Worker
// context. For more details, see https://crbug.com/1462384.
// This test is the same as ServiceWorkerBasedBackgroundTest.Basic.
IN_PROC_BROWSER_TEST_F(ControlledFrameServiceWorkerTest, PRE_Basic) {
  ExtensionTestMessageListener newtab_listener("CREATED");
  newtab_listener.set_failure_message("CREATE_FAILED");
  ExtensionTestMessageListener worker_listener("WORKER_RUNNING");
  worker_listener.set_failure_message("NON_WORKER_SCOPE");
  const extensions::Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII(
          "service_worker/worker_based_background/basic"));
  ASSERT_TRUE(extension);
  const extensions::ExtensionId extension_id = extension->id();
  EXPECT_TRUE(worker_listener.WaitUntilSatisfied());

  const GURL url =
      embedded_https_test_server().GetURL("/extensions/test_file.html");
  content::WebContents* new_web_contents =
      extensions::browsertest_util::AddTab(browser(), url);
  EXPECT_TRUE(new_web_contents);
  EXPECT_TRUE(newtab_listener.WaitUntilSatisfied());

  // Service Worker extension does not have ExtensionHost.
  EXPECT_FALSE(process_manager()->GetBackgroundHostForExtension(extension_id));
}

// After browser restarts, this test step ensures that opening a tab fires
// tabs.onCreated event listener to the extension without explicitly loading the
// extension. This is because the extension registered a listener before browser
// restarted in PRE_Basic.
IN_PROC_BROWSER_TEST_F(ControlledFrameServiceWorkerTest, Basic) {
  ExtensionTestMessageListener newtab_listener("CREATED");
  newtab_listener.set_failure_message("CREATE_FAILED");
  const GURL url =
      embedded_https_test_server().GetURL("/extensions/test_file.html");
  content::WebContents* new_web_contents =
      extensions::browsertest_util::AddTab(browser(), url);
  EXPECT_TRUE(new_web_contents);
  EXPECT_TRUE(newtab_listener.WaitUntilSatisfied());
}

class ControlledFrameNotAvailableChannelTest
    : public ControlledFrameApiTest,
      public testing::WithParamInterface<version_info::Channel> {
 protected:
  ControlledFrameNotAvailableChannelTest()
      : ControlledFrameApiTest(/*channel=*/GetParam(),
                               /*feature_setting=*/FeatureSetting::ENABLED,
                               /*flag_setting=*/FlagSetting::CONTROLLED_FRAME) {
  }
};

INSTANTIATE_TEST_SUITE_P(ControlledFrameNotAvailableChannels,
                         ControlledFrameNotAvailableChannelTest,
                         testing::Values(version_info::Channel::STABLE,
                                         version_info::Channel::BETA,
                                         version_info::Channel::DEV,
                                         version_info::Channel::CANARY,
                                         version_info::Channel::DEFAULT));

IN_PROC_BROWSER_TEST_P(ControlledFrameNotAvailableChannelTest, Test) {
  // Test if Controlled Frame is not available.
  const GURL start_url("https://app.site.test/example/index");
  InstallPWA(start_url);
  content::WebContents* app_contents =
      browser()->tab_strip_model()->GetActiveWebContents();

  ASSERT_FALSE(CreateControlledFrame(
      app_contents->GetPrimaryMainFrame(),
      embedded_https_test_server().GetURL("/index.html")));
}

class ControlledFrameAvailabilityTest
    : public ControlledFrameApiTest,
      public testing::WithParamInterface<
          ::std::tuple<version_info::Channel, FeatureSetting, FlagSetting>> {
 protected:
  ControlledFrameAvailabilityTest()
      : ControlledFrameApiTest(
            /*channel=*/std::get<0>(GetParam()),
            /*feature_setting=*/std::get<1>(GetParam()),
            /*flag_setting=*/std::get<2>(GetParam())) {}

  ~ControlledFrameAvailabilityTest() override = default;

  // |DetermineExpectedState| derives the expected enabling status based on
  // the channel, feature, and flag inputs.
  //
  // Ideally, when we set a feature to enabled or disabled in the test setup,
  // we would be altering that feature's default setting for the purposes of
  // the test. However, ScopedFeatureList doesn't enable or disable a feature
  // via defaults but instead by overrides. As a result, any feature that's
  // enabled or disabled by ScopedFeatureList will appear as an override.
  bool DetermineExpectedState() {
    return feature_setting() != FeatureSetting::DISABLED;
  }
};

INSTANTIATE_TEST_SUITE_P(
    /* */,
    ControlledFrameAvailabilityTest,
    /* Per-channel tests examine the extensions-based availability system. */
    testing::Combine(
        /*channel=*/testing::Values(version_info::Channel::STABLE,
                                    version_info::Channel::BETA,
                                    version_info::Channel::DEV,
                                    version_info::Channel::CANARY),
        /*feature=*/
        testing::Values(FeatureSetting::NONE,
                        FeatureSetting::DISABLED,
                        FeatureSetting::ENABLED),
        /*flag=*/
        testing::Values(FlagSetting::NONE,
                        FlagSetting::EXPERIMENTAL,
                        FlagSetting::CONTROLLED_FRAME)));

IN_PROC_BROWSER_TEST_P(ControlledFrameAvailabilityTest, Verify) {
  const bool expected_enabled = DetermineExpectedState();
  EXPECT_EQ(expected_enabled,
            base::FeatureList::IsEnabled(blink::features::kControlledFrame));

  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  const bool actual_enabled = CreateControlledFrame(
      app_frame, embedded_https_test_server().GetURL("/index.html"));
  EXPECT_EQ(expected_enabled, actual_enabled)
      << "Test failure for case: " << ConfigToString();

  // Uncomment for debugging information:
  // DLOG(ERROR) << ConfigToString();
  // DLOG(ERROR) << "expected_enabled=" << expected_enabled
  //             << "; actual_enabled=" << actual_enabled;

  if (expected_enabled && actual_enabled) {
    auto* web_view_guest = GetWebViewGuest(app_frame);
    EXPECT_EQ(kEvalSuccessStr, SetBackgroundColorToWhite(web_view_guest));
    EXPECT_EQ(kEvalSuccessStr, ExecuteScriptRedBackgroundCode(app_frame));
    EXPECT_EQ(kEvalSuccessStr, VerifyBackgroundColorIsRed(web_view_guest));
  }
}

class ControlledFrameAvailabilityAdminPolicyTest
    : public ControlledFrameApiTest,
      public testing::WithParamInterface<ContentSetting> {};

IN_PROC_BROWSER_TEST_P(ControlledFrameAvailabilityAdminPolicyTest,
                       VerifyPolicy) {
  // Get the expected content setting and set it up.
  const ContentSetting content_setting = GetParam();

  bool expected_enabled =
      content_setting != ContentSetting::CONTENT_SETTING_BLOCK;

  HostContentSettingsMapFactory::GetForProfile(profile())
      ->SetDefaultContentSetting(ContentSettingsType::CONTROLLED_FRAME,
                                 content_setting);

  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  const bool actual_enabled = CreateControlledFrame(
      app_frame, embedded_https_test_server().GetURL("/index.html"));
  EXPECT_EQ(expected_enabled, actual_enabled);
}

INSTANTIATE_TEST_SUITE_P(
    /* */,
    ControlledFrameAvailabilityAdminPolicyTest,
    /* Per-channel tests examine the extensions-based availability system. */
    testing::Values(ContentSetting::CONTENT_SETTING_DEFAULT,
                    ContentSetting::CONTENT_SETTING_ALLOW,
                    ContentSetting::CONTENT_SETTING_BLOCK));

class ControlledFrameRequestHeaderTest : public ControlledFrameTestBase {
 public:
  [[nodiscard]] bool SetUserAgentAndAwaitReload(content::RenderFrameHost* frame,
                                                const std::string& user_agent) {
    const std::string kRemoveUserAgentAndReload = R"(
new Promise((resolve, reject) => {
  const controlledframe = document.getElementsByTagName('controlledframe')[0];
  if (!('src' in controlledframe)) {
    reject('FAIL');
    return;
  }
  controlledframe.addEventListener('loadstop', resolve);
  controlledframe.addEventListener('loadabort', reject);
  // |setUserAgentOverride| should automatically reload.
  controlledframe.setUserAgentOverride($1);
});
    )";
    return ExecJs(frame,
                  content::JsReplace(kRemoveUserAgentAndReload, user_agent));
  }

  [[nodiscard]] bool SetClientHintsUABrandEnabled(
      content::RenderFrameHost* frame,
      bool enable) {
    const std::string kToggleClientHintsBrandAndReload = R"(
new Promise((resolve, reject) => {
  const controlledframe = document.getElementsByTagName('controlledframe')[0];
  if (!('src' in controlledframe)) {
    reject('FAIL');
    return;
  }
  controlledframe.addEventListener('loadstop', resolve);
  controlledframe.addEventListener('loadabort', reject);

  // |setClientHintsUABrandEnabled| should automatically reload.
  controlledframe.setClientHintsUABrandEnabled($1);
});
    )";
    return ExecJs(frame,
                  content::JsReplace(kToggleClientHintsBrandAndReload, enable));
  }

  void MonitorRequest(const net::test_server::HttpRequest& request) {
    if (request.relative_url != "/index.html") {
      return;
    }
    ASSERT_TRUE(request.headers.contains("User-Agent"));
    last_seen_ua_ = request.headers.at("User-Agent");
    ASSERT_TRUE(request.headers.contains("Sec-CH-UA"));
    last_seen_sec_ch_ua_ = request.headers.at("Sec-CH-UA");
  }

  const std::string& last_seen_ua() const { return last_seen_ua_; }
  const std::string& last_seen_sec_ch_ua() const {
    return last_seen_sec_ch_ua_;
  }

 private:
  std::string last_seen_ua_;
  std::string last_seen_sec_ch_ua_;
};

// Verifies that `setUserAgentOverride` works as expected, and that default
// Sec-CH-UA includes "ControlledFrame" brand.
IN_PROC_BROWSER_TEST_F(ControlledFrameRequestHeaderTest,
                       HasDefaultCHUABrandWithUAOverride) {
  embedded_https_test_server().RegisterRequestMonitor(
      base::BindRepeating(&ControlledFrameRequestHeaderTest::MonitorRequest,
                          base::Unretained(this)));

  StartContentServer("web_apps/simple_isolated_app");

  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  ASSERT_TRUE(CreateControlledFrame(
      app_frame, embedded_https_test_server().GetURL("/index.html")));
  EXPECT_EQ(last_seen_ua(), embedder_support::GetUserAgent());
  EXPECT_THAT(last_seen_sec_ch_ua(), HasSubstr("ControlledFrame"));

  ASSERT_TRUE(SetUserAgentAndAwaitReload(app_frame, "foobar"));
  EXPECT_EQ(last_seen_ua(), "foobar");
  EXPECT_THAT(last_seen_sec_ch_ua(), HasSubstr("ControlledFrame"));

  // Passing an empty string should reset the UA value.
  ASSERT_TRUE(SetUserAgentAndAwaitReload(app_frame, ""));
  EXPECT_EQ(last_seen_ua(), embedder_support::GetUserAgent());
  EXPECT_THAT(last_seen_sec_ch_ua(), HasSubstr("ControlledFrame"));
}

// `setClientHintsUABrandEnabled` toggles the `Sec-CH-UA` headers to Chrome
// default or Controlled Frame default.
IN_PROC_BROWSER_TEST_F(ControlledFrameRequestHeaderTest,
                       SetClientHintsUABrandEnabled) {
  embedded_https_test_server().RegisterRequestMonitor(
      base::BindRepeating(&ControlledFrameRequestHeaderTest::MonitorRequest,
                          base::Unretained(this)));

  StartContentServer("web_apps/simple_isolated_app");

  web_app::IsolatedWebAppUrlInfo url_info =
      CreateAndInstallEmptyApp(web_app::ManifestBuilder());
  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());

  ASSERT_TRUE(CreateControlledFrame(
      app_frame, embedded_https_test_server().GetURL("/index.html")));
  EXPECT_EQ(last_seen_ua(), embedder_support::GetUserAgent());
  EXPECT_THAT(last_seen_sec_ch_ua(), HasSubstr("ControlledFrame"));

  // Disables the "ControlledFrame" brand in CH-UA.
  ASSERT_TRUE(SetClientHintsUABrandEnabled(app_frame, false));
  EXPECT_EQ(last_seen_ua(), embedder_support::GetUserAgent());
  EXPECT_THAT(last_seen_sec_ch_ua(), Not(HasSubstr("ControlledFrame")));

  // Setting a custom UA should not reset the CH-UA.
  ASSERT_TRUE(SetUserAgentAndAwaitReload(app_frame, "foobar"));
  EXPECT_EQ(last_seen_ua(), "foobar");
  EXPECT_THAT(last_seen_sec_ch_ua(), Not(HasSubstr("ControlledFrame")));

  // Passing an empty string should reset the UA value.
  ASSERT_TRUE(SetUserAgentAndAwaitReload(app_frame, ""));
  EXPECT_EQ(last_seen_ua(), embedder_support::GetUserAgent());
  EXPECT_THAT(last_seen_sec_ch_ua(), Not(HasSubstr("ControlledFrame")));

  // Re-enables the "ControlledFrame" brand in the CH-UA.
  ASSERT_TRUE(SetClientHintsUABrandEnabled(app_frame, true));
  EXPECT_EQ(last_seen_ua(), embedder_support::GetUserAgent());
  EXPECT_THAT(last_seen_sec_ch_ua(), HasSubstr("ControlledFrame"));
}

}  // namespace controlled_frame