blob: 5746d26ff8418843b0e84f17f2fcdecb33effb79 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/ref_counted_memory.h"
#include "base/test/scoped_feature_list.h"
#include "content/browser/renderer_host/navigation_controller_impl.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/webui/web_ui_data_source_impl.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/url_utils.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/web_ui_browsertest_util.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
namespace content {
#if !BUILDFLAG(IS_ANDROID)
namespace {
class InitialWebUIOverrideContentBrowserClient
: public ContentBrowserTestContentBrowserClient {
public:
explicit InitialWebUIOverrideContentBrowserClient(
const GURL& initial_webui_url)
: initial_webui_url_(initial_webui_url) {}
InitialWebUIOverrideContentBrowserClient(
const InitialWebUIOverrideContentBrowserClient&) = delete;
InitialWebUIOverrideContentBrowserClient& operator=(
const InitialWebUIOverrideContentBrowserClient&) = delete;
bool IsInitialWebUIURL(const GURL& url) override {
return initial_webui_url_ == url;
}
private:
GURL initial_webui_url_;
};
class WebUITestWebUIControllerFactory : public WebUIControllerFactory {
public:
std::unique_ptr<WebUIController> CreateWebUIControllerForURL(
WebUI* web_ui,
const GURL& url) override {
return HasWebUIScheme(url) ? std::make_unique<WebUIController>(web_ui)
: nullptr;
}
WebUI::TypeID GetWebUIType(BrowserContext* browser_context,
const GURL& url) override {
return HasWebUIScheme(url) ? reinterpret_cast<WebUI::TypeID>(1) : nullptr;
}
bool UseWebUIForURL(BrowserContext* browser_context,
const GURL& url) override {
return HasWebUIScheme(url);
}
};
} // namespace
class InitialWebUINavigationBrowserTest : public ContentBrowserTest {
public:
InitialWebUINavigationBrowserTest() {
WebUIControllerFactory::RegisterFactory(&factory_);
scoped_feature_list_.InitWithFeatures(
{features::kInitialWebUISyncNavStartToCommit,
features::kWebUIInProcessResourceLoadingV2},
{});
}
protected:
WebContentsImpl* contents() const {
return static_cast<WebContentsImpl*>(shell()->web_contents());
}
private:
WebUITestWebUIControllerFactory factory_;
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(InitialWebUINavigationBrowserTest, CommitInitialWebUI) {
GURL url("chrome://foo");
// Make `url` an initial WebUI URL.
InitialWebUIOverrideContentBrowserClient content_browser_client(url);
// Create a new WebContents, since initial WebUI navigations are only allowed
// to happen as the first navigation in a new WebContents.
WebContents::CreateParams new_contents_params(contents()->GetBrowserContext(),
contents()->GetSiteInstance());
// Make sure the initial SiteInstance for the WebContents uses `url` as the
// site URL, rather than an empty URL. This is required to make sure that the
// renderer process flags are clear.
new_contents_params.site_instance =
SiteInstance::CreateForURL(contents()->GetBrowserContext(), url);
std::unique_ptr<WebContents> new_web_contents(
WebContents::Create(new_contents_params));
NavigationControllerImpl& controller =
static_cast<NavigationControllerImpl&>(new_web_contents->GetController());
// Setup a fake resource path for the body. Note that we're adding a direct
// string response instead of adding a ResourceBundle resource ID. This is
// different from what happens in production (which will use a resource ID),
// but this is significantly easier to test.
WebUIDataSource* source =
WebUIDataSource::CreateAndAdd(controller.GetBrowserContext(), "foo");
source->SetResourcePathToResponse("", "<!doctype html><body>bar</body>");
// Ensure that there are no navigations yet on the new WebContents.
FrameTreeNode* root = static_cast<WebContentsImpl*>(new_web_contents.get())
->GetPrimaryFrameTree()
.root();
EXPECT_FALSE(root->navigation_request());
// Navigate to `url`.
TestNavigationObserver navigation_observer(url);
navigation_observer.WatchExistingWebContents();
controller.LoadURLWithParams(NavigationController::LoadURLParams(url));
// The navigation reached CommitNavigation synchronously, since it is now
// owned by the primary main RenderFrameHost instead of the root
// FrameTreeNode.
EXPECT_FALSE(root->navigation_request());
EXPECT_TRUE(
root->current_frame_host()->HasPendingCommitForCrossDocumentNavigation());
{
std::vector<base::SafeRef<NavigationHandle>>
committing_navigation_requests =
root->current_frame_host()
->GetPendingCommitCrossDocumentNavigations();
EXPECT_EQ(1u, committing_navigation_requests.size());
EXPECT_EQ(url, committing_navigation_requests[0]->GetURL());
}
navigation_observer.Wait();
// Ensure the navigation successfully commits and loads the body HTML.
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
EXPECT_EQ(navigation_observer.last_navigation_url(), url);
RenderFrameHostImplWrapper rfh(root->current_frame_host());
EXPECT_EQ("bar", EvalJs(rfh.get(), "document.body.innerHTML"));
// Ensure that the process has the correct flag set.
EXPECT_TRUE(rfh->GetProcess()->IsForInitialWebUI());
// Check that CSP was set.
{
const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp =
rfh->policy_container_host()->policies().content_security_policies;
EXPECT_EQ(1u, root_csp.size());
EXPECT_EQ(
"child-src 'none';"
"object-src 'none';"
"require-trusted-types-for 'script';"
"script-src chrome://resources 'self';"
"trusted-types;frame-ancestors 'none';",
root_csp[0]->header->header_value);
}
}
#endif
} // namespace content