|  | // Copyright 2014 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include <limits> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/containers/flat_map.h" | 
|  | #include "base/containers/span.h" | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/functional/callback.h" | 
|  | #include "base/memory/raw_ptr.h" | 
|  | #include "base/memory/ref_counted_memory.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/threading/thread_restrictions.h" | 
|  | #include "base/unguessable_token.h" | 
|  | #include "build/build_config.h" | 
|  | #include "content/browser/webui/web_ui_controller_factory_registry.h" | 
|  | #include "content/browser/webui/web_ui_impl.h" | 
|  | #include "content/public/browser/browser_context.h" | 
|  | #include "content/public/browser/render_frame_host.h" | 
|  | #include "content/public/browser/render_process_host.h" | 
|  | #include "content/public/browser/render_view_host.h" | 
|  | #include "content/public/browser/web_contents.h" | 
|  | #include "content/public/browser/web_ui.h" | 
|  | #include "content/public/browser/web_ui_controller.h" | 
|  | #include "content/public/browser/web_ui_controller_interface_binder.h" | 
|  | #include "content/public/browser/web_ui_data_source.h" | 
|  | #include "content/public/common/bindings_policy.h" | 
|  | #include "content/public/common/content_client.h" | 
|  | #include "content/public/common/referrer.h" | 
|  | #include "content/public/common/url_constants.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/no_renderer_crashes_assertion.h" | 
|  | #include "content/public/test/scoped_web_ui_controller_factory_registration.h" | 
|  | #include "content/public/test/test_navigation_observer.h" | 
|  | #include "content/public/test/test_utils.h" | 
|  | #include "content/shell/browser/shell.h" | 
|  | #include "content/test/data/web_ui_ts_test.test-mojom.h" | 
|  | #include "content/test/data/web_ui_ts_test_types.test-mojom.h" | 
|  | #include "content/test/grit/web_ui_mojo_test_resources.h" | 
|  | #include "content/test/grit/web_ui_mojo_test_resources_map.h" | 
|  | #include "mojo/public/cpp/bindings/binder_map.h" | 
|  | #include "mojo/public/cpp/bindings/pending_receiver.h" | 
|  | #include "mojo/public/cpp/bindings/receiver.h" | 
|  | #include "mojo/public/cpp/bindings/self_owned_receiver.h" | 
|  | #include "mojo/public/mojom/base/time.mojom.h" | 
|  | #include "third_party/blink/public/common/chrome_debug_urls.h" | 
|  |  | 
|  | namespace content { | 
|  | namespace { | 
|  |  | 
|  | const char kMojoWebUiTsHost[] = "mojo-web-ui-ts"; | 
|  | const char kDummyWebUiHost[] = "dummy-web-ui"; | 
|  |  | 
|  | class WebUITsMojoTestCacheImpl : public mojom::WebUITsMojoTestCache { | 
|  | public: | 
|  | explicit WebUITsMojoTestCacheImpl( | 
|  | mojo::PendingReceiver<mojom::WebUITsMojoTestCache> receiver) | 
|  | : receiver_(this, std::move(receiver)) {} | 
|  |  | 
|  | ~WebUITsMojoTestCacheImpl() override = default; | 
|  |  | 
|  | // mojom::WebUITsMojoTestCache overrides: | 
|  | void Put(const GURL& url, const std::string& contents) override { | 
|  | cache_[url] = contents; | 
|  | } | 
|  |  | 
|  | void GetAll(GetAllCallback callback) override { | 
|  | std::vector<mojom::TsCacheItemPtr> items; | 
|  | for (const auto& entry : cache_) | 
|  | items.push_back(mojom::TsCacheItem::New(entry.first, entry.second)); | 
|  | std::move(callback).Run(std::move(items)); | 
|  | } | 
|  |  | 
|  | void Echo( | 
|  | std::optional<bool> optional_bool, | 
|  | std::optional<uint8_t> optional_uint8, | 
|  | std::optional<mojom::TestEnum> optional_enum, | 
|  | mojom::OptionalNumericsStructPtr optional_numerics, | 
|  | const std::vector<std::optional<bool>>& optional_bools, | 
|  | const std::vector<std::optional<uint32_t>>& optional_ints, | 
|  | const std::vector<std::optional<mojom::TestEnum>>& optional_enums, | 
|  | const base::flat_map<int32_t, std::optional<bool>>& bool_map, | 
|  | const base::flat_map<int32_t, std::optional<int32_t>>& int_map, | 
|  | const base::flat_map<int32_t, std::optional<mojom::TestEnum>>& enum_map, | 
|  | mojom::SimpleMappedTypePtr simple_mapped, | 
|  | mojom::NestedMappedTypePtr nested_mapped, | 
|  | mojom::StringDictPtr dict_ptr, | 
|  | EchoCallback callback) override { | 
|  | std::move(callback).Run( | 
|  | optional_bool.has_value() ? std::make_optional(!optional_bool.value()) | 
|  | : std::nullopt, | 
|  | optional_uint8.has_value() ? std::make_optional(~optional_uint8.value()) | 
|  | : std::nullopt, | 
|  | optional_enum.has_value() ? std::make_optional(mojom::TestEnum::kTwo) | 
|  | : std::nullopt, | 
|  | mojom::OptionalNumericsStruct::New( | 
|  | optional_numerics->optional_bool.has_value() | 
|  | ? std::make_optional(!optional_numerics->optional_bool.value()) | 
|  | : std::nullopt, | 
|  | optional_numerics->optional_uint8.has_value() | 
|  | ? std::make_optional(~optional_numerics->optional_uint8.value()) | 
|  | : std::nullopt, | 
|  | optional_numerics->optional_enum.has_value() | 
|  | ? std::make_optional(mojom::TestEnum::kTwo) | 
|  | : std::nullopt), | 
|  | optional_bools, optional_ints, optional_enums, bool_map, int_map, | 
|  | enum_map, simple_mapped->Clone(), nested_mapped->Clone(), | 
|  | dict_ptr ? dict_ptr->Clone() : nullptr); | 
|  | } | 
|  |  | 
|  | void EchoTypemaps(base::Time time, | 
|  | const base::UnguessableToken& token, | 
|  | EchoTypemapsCallback cb) override { | 
|  | std::move(cb).Run(time, token); | 
|  | } | 
|  |  | 
|  | void EchoOptionalTypemaps(mojom::OptionalTypemapPtr container, | 
|  | EchoOptionalTypemapsCallback cb) override { | 
|  | std::move(cb).Run(container->Clone()); | 
|  | } | 
|  |  | 
|  | void AddStringWrapper( | 
|  | mojo::PendingRemote<mojom::StringWrapper> string_wrapper) override { | 
|  | string_wrapper_list_.push_back( | 
|  | mojo::Remote<mojom::StringWrapper>(std::move(string_wrapper))); | 
|  | } | 
|  |  | 
|  | void GetStringWrapperList(GetStringWrapperListCallback cb) override { | 
|  | std::vector<mojo::PendingRemote<mojom::StringWrapper>> string_wrapper_list; | 
|  | for (auto& string_wrapper : string_wrapper_list_) { | 
|  | mojo::PendingRemote<mojom::StringWrapper> cloned_string_wrapper; | 
|  | string_wrapper->Clone( | 
|  | cloned_string_wrapper.InitWithNewPipeAndPassReceiver()); | 
|  | string_wrapper_list.emplace_back(std::move(cloned_string_wrapper)); | 
|  | } | 
|  |  | 
|  | std::move(cb).Run(std::move(string_wrapper_list)); | 
|  | } | 
|  |  | 
|  | void GetAssociatedReceiver(GetAssociatedReceiverCallback cb) override { | 
|  | CHECK(!test_client_.is_bound()) << "This method can only be called once"; | 
|  | std::move(cb).Run(test_client_.BindNewEndpointAndPassReceiver()); | 
|  | test_client_->BlockUntilBound(); | 
|  | } | 
|  |  | 
|  | void Ping(PingCallback cb) override { std::move(cb).Run("ping"); } | 
|  |  | 
|  | private: | 
|  | mojo::Receiver<mojom::WebUITsMojoTestCache> receiver_; | 
|  | std::map<GURL, std::string> cache_; | 
|  | std::vector<mojo::Remote<mojom::StringWrapper>> string_wrapper_list_; | 
|  | mojo::AssociatedRemote<mojom::TestAssociatedClient> test_client_; | 
|  | }; | 
|  |  | 
|  | // WebUIController that sets up mojo bindings. | 
|  | class TestWebUIController : public WebUIController { | 
|  | public: | 
|  | explicit TestWebUIController(WebUI* web_ui, | 
|  | BindingsPolicySet bindings = BindingsPolicySet( | 
|  | {BindingsPolicyValue::kMojoWebUi})) | 
|  | : WebUIController(web_ui) { | 
|  | web_ui->SetBindings(bindings); | 
|  | { | 
|  | WebUIDataSource* data_source = WebUIDataSource::CreateAndAdd( | 
|  | web_ui->GetWebContents()->GetBrowserContext(), kMojoWebUiTsHost); | 
|  | data_source->OverrideContentSecurityPolicy( | 
|  | network::mojom::CSPDirectiveName::ScriptSrc, | 
|  | "script-src chrome://resources 'self' 'unsafe-eval';"); | 
|  | data_source->DisableTrustedTypesCSP(); | 
|  | data_source->AddResourcePaths(kWebUiMojoTestResources); | 
|  | data_source->AddResourcePath("", IDR_WEB_UI_MOJO_TS_HTML); | 
|  | } | 
|  | { | 
|  | WebUIDataSource* data_source = WebUIDataSource::CreateAndAdd( | 
|  | web_ui->GetWebContents()->GetBrowserContext(), kDummyWebUiHost); | 
|  | data_source->SetRequestFilter( | 
|  | base::BindRepeating([](const std::string& path) { return true; }), | 
|  | base::BindRepeating([](const std::string& id, | 
|  | WebUIDataSource::GotDataCallback callback) { | 
|  | std::move(callback).Run(new base::RefCountedString); | 
|  | })); | 
|  | } | 
|  | } | 
|  |  | 
|  | TestWebUIController(const TestWebUIController&) = delete; | 
|  | TestWebUIController& operator=(const TestWebUIController&) = delete; | 
|  |  | 
|  | protected: | 
|  | std::unique_ptr<WebUITsMojoTestCacheImpl> ts_cache_; | 
|  | }; | 
|  |  | 
|  | // TestWebUIController that can bind a WebUITsMojoTestCache interface when | 
|  | // requested by the page. | 
|  | class CacheTestWebUIController : public TestWebUIController { | 
|  | public: | 
|  | explicit CacheTestWebUIController(WebUI* web_ui) | 
|  | : TestWebUIController(web_ui) {} | 
|  | ~CacheTestWebUIController() override = default; | 
|  |  | 
|  | void BindInterface( | 
|  | mojo::PendingReceiver<mojom::WebUITsMojoTestCache> receiver) { | 
|  | ts_cache_ = std::make_unique<WebUITsMojoTestCacheImpl>(std::move(receiver)); | 
|  | } | 
|  |  | 
|  | WEB_UI_CONTROLLER_TYPE_DECL(); | 
|  | }; | 
|  |  | 
|  | WEB_UI_CONTROLLER_TYPE_IMPL(CacheTestWebUIController) | 
|  |  | 
|  | // WebUIControllerFactory that creates TestWebUIController. | 
|  | class TestWebUIControllerFactory : public WebUIControllerFactory { | 
|  | public: | 
|  | TestWebUIControllerFactory() | 
|  | : registered_controllers_( | 
|  | {{"cache", base::BindRepeating( | 
|  | &TestWebUIControllerFactory::CreateCacheController, | 
|  | base::Unretained(this))}, | 
|  | {"hybrid", base::BindRepeating( | 
|  | &TestWebUIControllerFactory::CreateHybridController, | 
|  | base::Unretained(this))}, | 
|  | {"webui_bindings", | 
|  | base::BindRepeating( | 
|  | &TestWebUIControllerFactory::CreateWebUIController, | 
|  | base::Unretained(this))}}) {} | 
|  |  | 
|  | TestWebUIControllerFactory(const TestWebUIControllerFactory&) = delete; | 
|  | TestWebUIControllerFactory& operator=(const TestWebUIControllerFactory&) = | 
|  | delete; | 
|  |  | 
|  | std::unique_ptr<WebUIController> CreateWebUIControllerForURL( | 
|  | WebUI* web_ui, | 
|  | const GURL& url) override { | 
|  | if (!web_ui_enabled_ || !url.SchemeIs(kChromeUIScheme)) | 
|  | return nullptr; | 
|  |  | 
|  | auto it = registered_controllers_.find(url.GetQuery()); | 
|  | if (it != registered_controllers_.end()) | 
|  | return it->second.Run(web_ui); | 
|  |  | 
|  | return std::make_unique<TestWebUIController>(web_ui); | 
|  | } | 
|  |  | 
|  | WebUI::TypeID GetWebUIType(BrowserContext* browser_context, | 
|  | const GURL& url) override { | 
|  | if (!web_ui_enabled_ || !url.SchemeIs(kChromeUIScheme)) | 
|  | return WebUI::kNoWebUI; | 
|  |  | 
|  | return reinterpret_cast<WebUI::TypeID>(1); | 
|  | } | 
|  |  | 
|  | bool UseWebUIForURL(BrowserContext* browser_context, | 
|  | const GURL& url) override { | 
|  | return GetWebUIType(browser_context, url) != WebUI::kNoWebUI; | 
|  | } | 
|  |  | 
|  | void set_web_ui_enabled(bool enabled) { web_ui_enabled_ = enabled; } | 
|  |  | 
|  | private: | 
|  | std::unique_ptr<WebUIController> CreateCacheController(WebUI* web_ui) { | 
|  | return std::make_unique<CacheTestWebUIController>(web_ui); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<WebUIController> CreateHybridController(WebUI* web_ui) { | 
|  | return std::make_unique<TestWebUIController>(web_ui, | 
|  | kWebUIBindingsPolicySet); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<WebUIController> CreateWebUIController(WebUI* web_ui) { | 
|  | return std::make_unique<TestWebUIController>( | 
|  | web_ui, BindingsPolicySet({BindingsPolicyValue::kWebUi})); | 
|  | } | 
|  |  | 
|  | bool web_ui_enabled_ = true; | 
|  | const base::flat_map< | 
|  | std::string, | 
|  | base::RepeatingCallback<std::unique_ptr<WebUIController>(WebUI*)>> | 
|  | registered_controllers_; | 
|  | }; | 
|  |  | 
|  | class StringWrapperImpl : public mojom::StringWrapper { | 
|  | public: | 
|  | StringWrapperImpl() = default; | 
|  |  | 
|  | // mojom::StringWrapper | 
|  | void PutString(const std::string& item) override { item_ = item; } | 
|  |  | 
|  | void GetString(GetStringCallback cb) override { std::move(cb).Run(item_); } | 
|  |  | 
|  | void Clone( | 
|  | mojo::PendingReceiver<mojom::StringWrapper> clone_receiver) override { | 
|  | receivers_.Add(this, std::move(clone_receiver)); | 
|  | } | 
|  |  | 
|  | static void Create(mojo::PendingReceiver<mojom::StringWrapper> receiver) { | 
|  | mojo::MakeSelfOwnedReceiver(std::make_unique<StringWrapperImpl>(), | 
|  | std::move(receiver)); | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::string item_; | 
|  | mojo::ReceiverSet<mojom::StringWrapper> receivers_; | 
|  | }; | 
|  |  | 
|  | class TestWebUIContentBrowserClient | 
|  | : public ContentBrowserTestContentBrowserClient { | 
|  | public: | 
|  | TestWebUIContentBrowserClient() {} | 
|  | TestWebUIContentBrowserClient(const TestWebUIContentBrowserClient&) = delete; | 
|  | TestWebUIContentBrowserClient& operator=( | 
|  | const TestWebUIContentBrowserClient&) = delete; | 
|  | ~TestWebUIContentBrowserClient() override {} | 
|  |  | 
|  | void RegisterBrowserInterfaceBindersForFrame( | 
|  | RenderFrameHost* render_frame_host, | 
|  | mojo::BinderMapWithContext<content::RenderFrameHost*>* map) override { | 
|  | RegisterWebUIControllerInterfaceBinder<mojom::WebUITsMojoTestCache, | 
|  | CacheTestWebUIController>(map); | 
|  |  | 
|  | map->Add<content::mojom::StringWrapper>( | 
|  | &TestWebUIContentBrowserClient::BindStringWrapper); | 
|  | } | 
|  |  | 
|  | static void BindStringWrapper( | 
|  | RenderFrameHost* render_frame_host, | 
|  | mojo::PendingReceiver<mojom::StringWrapper> receiver) { | 
|  | StringWrapperImpl::Create(std::move(receiver)); | 
|  | } | 
|  | }; | 
|  |  | 
|  | class WebUIMojoTest : public ContentBrowserTest { | 
|  | public: | 
|  | WebUIMojoTest() = default; | 
|  |  | 
|  | WebUIMojoTest(const WebUIMojoTest&) = delete; | 
|  | WebUIMojoTest& operator=(const WebUIMojoTest&) = delete; | 
|  |  | 
|  | TestWebUIControllerFactory* factory() { return &factory_; } | 
|  |  | 
|  | void NavigateWithNewWebUI(const std::string& path) { | 
|  | // Load a dummy WebUI URL first so that a new WebUI is set up when we load | 
|  | // the URL we're actually interested in. | 
|  | EXPECT_TRUE(NavigateToURL(shell(), GetWebUIURL(kDummyWebUiHost))); | 
|  | EXPECT_TRUE(NavigateToURL( | 
|  | shell(), GetWebUIURL(GetMojoWebUiHost() + std::string("/") + path))); | 
|  | } | 
|  |  | 
|  | // Run |script| and return a boolean result. | 
|  | bool RunBoolFunction(const std::string& script) { | 
|  | return EvalJs(shell()->web_contents(), script).ExtractBool(); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | std::string GetMojoWebUiHost() { return kMojoWebUiTsHost; } | 
|  |  | 
|  | void SetUpOnMainThread() override { | 
|  | client_ = std::make_unique<TestWebUIContentBrowserClient>(); | 
|  | } | 
|  |  | 
|  | void TearDownOnMainThread() override { client_.reset(); } | 
|  |  | 
|  | private: | 
|  | TestWebUIControllerFactory factory_; | 
|  | content::ScopedWebUIControllerFactoryRegistration factory_registration_{ | 
|  | &factory_}; | 
|  | std::unique_ptr<TestWebUIContentBrowserClient> client_; | 
|  | }; | 
|  |  | 
|  | // Loads a WebUI page that contains Mojo JS bindings and verifies a message | 
|  | // round-trip between the page and the browser. | 
|  | IN_PROC_BROWSER_TEST_F(WebUIMojoTest, EndToEndCommunication) { | 
|  | // Load a dummy page in the initial RenderFrameHost.  The initial | 
|  | // RenderFrameHost is created by the test harness prior to installing | 
|  | // TestWebUIContentBrowserClient in WebUIMojoTest::SetUpOnMainThread().  If we | 
|  | // were to navigate that initial RFH to WebUI directly, it would get reused, | 
|  | // but it wouldn't have the test's browser interface binders (registered via | 
|  | // TestWebUIContentBrowserClient::RegisterBrowserInterfaceBindersForFrame() at | 
|  | // RFH creation time).  Navigating the initial RFH to some other page forces | 
|  | // the subsequent WebUI navigation to create a new RenderFrameHost, and by | 
|  | // this time, TestWebUIContentBrowserClient will take effect on that new RFH. | 
|  | EXPECT_TRUE(NavigateToURL(shell(), GURL("data:,foo"))); | 
|  |  | 
|  | GURL kTestUrl(GetWebUIURL(GetMojoWebUiHost() + "/?cache")); | 
|  | const std::string kTestScript = "runTest();"; | 
|  | EXPECT_TRUE(NavigateToURL(shell(), kTestUrl)); | 
|  | EXPECT_EQ(true, EvalJs(shell()->web_contents(), kTestScript)); | 
|  |  | 
|  | // Check that a second shell works correctly. | 
|  | Shell* other_shell = CreateBrowser(); | 
|  | EXPECT_TRUE(WaitForLoadStop(other_shell->web_contents())); | 
|  | EXPECT_TRUE(NavigateToURL(other_shell, kTestUrl)); | 
|  | EXPECT_EQ(true, EvalJs(other_shell->web_contents(), kTestScript)); | 
|  |  | 
|  | // Close the second shell and wait until the second shell exits. | 
|  | RenderFrameHostWrapper wrapper( | 
|  | other_shell->web_contents()->GetPrimaryMainFrame()); | 
|  | other_shell->Close(); | 
|  | EXPECT_TRUE(wrapper.WaitUntilRenderFrameDeleted()); | 
|  |  | 
|  | // Check that a third shell works correctly, even if we force it to share a | 
|  | // process with the first shell, by forcing an artificially low process | 
|  | // limit. | 
|  | RenderProcessHost::SetMaxRendererProcessCount(1); | 
|  |  | 
|  | // Subtle: provide an explicit initial SiteInstance, since otherwise the WebUI | 
|  | // will stay in the initial RFH's process and avoid process reuse needed for | 
|  | // this test. | 
|  | other_shell = Shell::CreateNewWindow( | 
|  | shell()->web_contents()->GetBrowserContext(), GURL(), | 
|  | SiteInstance::CreateForURL(shell()->web_contents()->GetBrowserContext(), | 
|  | kTestUrl), | 
|  | gfx::Size()); | 
|  | EXPECT_TRUE(NavigateToURL(other_shell, kTestUrl)); | 
|  | EXPECT_EQ(shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(), | 
|  | other_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); | 
|  | EXPECT_EQ(true, EvalJs(other_shell->web_contents(), kTestScript)); | 
|  | } | 
|  |  | 
|  | // Disabled due to flakiness: crbug.com/860385. | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | #define MAYBE_NativeMojoAvailable DISABLED_NativeMojoAvailable | 
|  | #else | 
|  | #define MAYBE_NativeMojoAvailable NativeMojoAvailable | 
|  | #endif | 
|  | IN_PROC_BROWSER_TEST_F(WebUIMojoTest, MAYBE_NativeMojoAvailable) { | 
|  | // Mojo bindings should be enabled. | 
|  | NavigateWithNewWebUI("web_ui_mojo_native.html"); | 
|  | EXPECT_TRUE(RunBoolFunction("isNativeMojoAvailable()")); | 
|  |  | 
|  | // Now navigate again with normal WebUI bindings and ensure chrome.send is | 
|  | // available. | 
|  | NavigateWithNewWebUI("web_ui_mojo_native.html?webui_bindings"); | 
|  | EXPECT_FALSE(RunBoolFunction("isNativeMojoAvailable()")); | 
|  |  | 
|  | // Now navigate again both WebUI and Mojo bindings and ensure chrome.send is | 
|  | // available. | 
|  | NavigateWithNewWebUI("web_ui_mojo_native.html?hybrid"); | 
|  | EXPECT_TRUE(RunBoolFunction("isNativeMojoAvailable()")); | 
|  |  | 
|  | // Now navigate again with WebUI disabled and ensure the native bindings are | 
|  | // not available. | 
|  | factory()->set_web_ui_enabled(false); | 
|  | NavigateWithNewWebUI("web_ui_mojo_native.html?hybrid"); | 
|  | EXPECT_FALSE(RunBoolFunction("isNativeMojoAvailable()")); | 
|  | } | 
|  |  | 
|  | // Disabled due to flakiness: crbug.com/860385. | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | #define MAYBE_ChromeSendAvailable DISABLED_ChromeSendAvailable | 
|  | #else | 
|  | #define MAYBE_ChromeSendAvailable ChromeSendAvailable | 
|  | #endif | 
|  | IN_PROC_BROWSER_TEST_F(WebUIMojoTest, MAYBE_ChromeSendAvailable) { | 
|  | // chrome.send is not available on mojo-only WebUIs. | 
|  | NavigateWithNewWebUI("web_ui_mojo_native.html"); | 
|  | EXPECT_FALSE(RunBoolFunction("isChromeSendAvailable()")); | 
|  |  | 
|  | // Now navigate again with normal WebUI bindings and ensure chrome.send is | 
|  | // available. | 
|  | NavigateWithNewWebUI("web_ui_mojo_native.html?webui_bindings"); | 
|  | EXPECT_TRUE(RunBoolFunction("isChromeSendAvailable()")); | 
|  |  | 
|  | // Now navigate again both WebUI and Mojo bindings and ensure chrome.send is | 
|  | // available. | 
|  | NavigateWithNewWebUI("web_ui_mojo_native.html?hybrid"); | 
|  | EXPECT_TRUE(RunBoolFunction("isChromeSendAvailable()")); | 
|  |  | 
|  | // Now navigate again with WebUI disabled and ensure that chrome.send is | 
|  | // not available. | 
|  | factory()->set_web_ui_enabled(false); | 
|  | NavigateWithNewWebUI("web_ui_mojo_native.html?hybrid"); | 
|  | EXPECT_FALSE(RunBoolFunction("isChromeSendAvailable()")); | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/440535492): Flaky on Win dbg. Re-enable this test. | 
|  | #if BUILDFLAG(IS_WIN) && !defined(NDEBUG) | 
|  | #define MAYBE_ChromeSendAvailable_AfterCrash \ | 
|  | DISABLED_ChromeSendAvailable_AfterCrash | 
|  | #else | 
|  | #define MAYBE_ChromeSendAvailable_AfterCrash ChromeSendAvailable_AfterCrash | 
|  | #endif | 
|  | IN_PROC_BROWSER_TEST_F(WebUIMojoTest, MAYBE_ChromeSendAvailable_AfterCrash) { | 
|  | GURL test_url(GetWebUIURL(GetMojoWebUiHost() + | 
|  | "/web_ui_mojo_native.html?webui_bindings")); | 
|  |  | 
|  | // Navigate with normal WebUI bindings and ensure chrome.send is available. | 
|  | EXPECT_TRUE(NavigateToURL(shell(), test_url)); | 
|  | EXPECT_TRUE(EvalJs(shell(), "isChromeSendAvailable()").ExtractBool()); | 
|  |  | 
|  | WebUIImpl* web_ui = static_cast<WebUIImpl*>( | 
|  | shell()->web_contents()->GetPrimaryMainFrame()->GetWebUI()); | 
|  |  | 
|  | // Simulate a crash on the page. | 
|  | content::ScopedAllowRendererCrashes allow_renderer_crashes(shell()); | 
|  | RenderProcessHostWatcher crash_observer( | 
|  | shell()->web_contents(), | 
|  | RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
|  | shell()->web_contents()->GetController().LoadURL( | 
|  | GURL(blink::kChromeUICrashURL), content::Referrer(), | 
|  | ui::PAGE_TRANSITION_TYPED, std::string()); | 
|  | crash_observer.Wait(); | 
|  | EXPECT_FALSE(web_ui->GetRemoteForTest().is_bound()); | 
|  |  | 
|  | // Now navigate again both WebUI and Mojo bindings and ensure chrome.send is | 
|  | // available. | 
|  | EXPECT_TRUE(NavigateToURL(shell(), test_url)); | 
|  | EXPECT_TRUE(EvalJs(shell(), "isChromeSendAvailable()").ExtractBool()); | 
|  | // The RenderFrameHost has been replaced after the crash, so get web_ui again. | 
|  | web_ui = static_cast<WebUIImpl*>( | 
|  | shell()->web_contents()->GetPrimaryMainFrame()->GetWebUI()); | 
|  | EXPECT_TRUE(web_ui->GetRemoteForTest().is_bound()); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace content |