From 0d31030c68ebe878bba9e0dee4b26f721c80b744 Mon Sep 17 00:00:00 2001 From: Jackson Tadie Date: Tue, 14 Jun 2022 16:09:08 +0000 Subject: [PATCH] Reland "Source Demo Mode SWA content from downloaded demo-mode-app Chrome Component" This is a reland of commit 43d80ae0d5091af45c4629abb61e09804e192855 Target //ash/webui/demo_mode_app_ui:unit_tests is now properly only included for unofficial builds Original change's description: > Source Demo Mode SWA content from downloaded demo-mode-app Chrome Component > > We use a WebUIDataSource with a RequestFilter instead of a separate > URLDataSource because we want some built-in resources for e.g. > exposing a JS bridge to our Mojo APIs. > > Change-Id: I31fea5f5c2d2dca25cf02895a3dfb1004dccc988 > Bug: b/228388936 > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3575540 > Commit-Queue: Jackson Tadie > Reviewed-by: Giovanni Ortuno Urquidi > Cr-Commit-Position: refs/heads/main@{#1013047} Bug: b/228388936 Change-Id: Ie4c444445b989519430ee24959d4fcff415498e8 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3703699 Commit-Queue: Jackson Tadie Reviewed-by: Christopher Lam Cr-Commit-Position: refs/heads/main@{#1013991} --- ash/webui/BUILD.gn | 4 + ash/webui/demo_mode_app_ui/BUILD.gn | 21 ++-- .../demo_mode_app_ui/demo_mode_app_ui.cc | 72 +++++++++-- ash/webui/demo_mode_app_ui/demo_mode_app_ui.h | 24 +++- .../demo_mode_app_ui_unittests.cc | 112 ++++++++++++++++++ ash/webui/demo_mode_app_ui/test/DEPS | 4 - .../test/demo_mode_app_ui_browsertest.js | 31 ----- ash/webui/demo_mode_app_ui/url_constants.cc | 1 + .../ash/login/demo_mode/demo_session.cc | 24 ++++ .../ash/login/demo_mode/demo_session.h | 12 ++ .../demo_mode_app_integration_browsertest.cc | 68 ++++++++++- .../webui/chrome_web_ui_configs_chromeos.cc | 5 +- chrome/test/BUILD.gn | 5 +- 13 files changed, 323 insertions(+), 60 deletions(-) create mode 100644 ash/webui/demo_mode_app_ui/demo_mode_app_ui_unittests.cc delete mode 100644 ash/webui/demo_mode_app_ui/test/DEPS delete mode 100644 ash/webui/demo_mode_app_ui/test/demo_mode_app_ui_browsertest.js diff --git a/ash/webui/BUILD.gn b/ash/webui/BUILD.gn index 230341e8e34c20..d804503839eeba 100644 --- a/ash/webui/BUILD.gn +++ b/ash/webui/BUILD.gn @@ -46,6 +46,10 @@ test("ash_webui_unittests") { "//ui/gl:test_support", ] + if (!is_official_build) { + deps += [ "//ash/webui/demo_mode_app_ui:unit_tests" ] + } + # Tests for Camera App only work on Chrome OS device but not linux-chromeos. if (is_chromeos_device) { deps += [ "//ash/webui/camera_app_ui:unit_tests" ] diff --git a/ash/webui/demo_mode_app_ui/BUILD.gn b/ash/webui/demo_mode_app_ui/BUILD.gn index 6022034e65814a..efc169293c2aac 100644 --- a/ash/webui/demo_mode_app_ui/BUILD.gn +++ b/ash/webui/demo_mode_app_ui/BUILD.gn @@ -3,7 +3,6 @@ # found in the LICENSE file. import("//build/config/chromeos/ui_mode.gni") -import("//chrome/test/base/js2gtest.gni") import("//third_party/closure_compiler/compile_js.gni") import("//tools/grit/preprocess_if_expr.gni") import("//ui/webui/resources/tools/generate_grd.gni") @@ -31,6 +30,18 @@ static_library("demo_mode_app_ui") { ] } +source_set("unit_tests") { + testonly = true + sources = [ "demo_mode_app_ui_unittests.cc" ] + deps = [ + ":demo_mode_app_ui", + "//base", + "//base/test:test_support", + "//testing/gtest", + "//url", + ] +} + js_type_check("closure_compile") { deps = [ ":app" ] closure_flags = default_closure_args + mojom_js_args @@ -41,14 +52,6 @@ js_library("app") { deps = [ "//ash/webui/demo_mode_app_ui/mojom:mojom_webui_js" ] } -js2gtest("browser_tests_js") { - test_type = "mojo_lite_webui" - - sources = [ "test/demo_mode_app_ui_browsertest.js" ] - - defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] -} - grd_prefix = "ash_demo_mode_app" mojo_grdp_file = "$target_gen_dir/demo_mode_app_mojo_resources.grdp" diff --git a/ash/webui/demo_mode_app_ui/demo_mode_app_ui.cc b/ash/webui/demo_mode_app_ui/demo_mode_app_ui.cc index 7af283520c5202..c5534d8c0994dd 100644 --- a/ash/webui/demo_mode_app_ui/demo_mode_app_ui.cc +++ b/ash/webui/demo_mode_app_ui/demo_mode_app_ui.cc @@ -4,11 +4,17 @@ #include "ash/webui/demo_mode_app_ui/demo_mode_app_ui.h" +#include + #include "ash/constants/ash_features.h" #include "ash/webui/demo_mode_app_ui/demo_mode_page_handler.h" #include "ash/webui/demo_mode_app_ui/url_constants.h" #include "ash/webui/grit/ash_demo_mode_app_resources.h" #include "ash/webui/grit/ash_demo_mode_app_resources_map.h" +#include "base/containers/fixed_flat_set.h" +#include "base/files/file_util.h" +#include "base/memory/ref_counted_memory.h" +#include "base/task/thread_pool.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_ui_data_source.h" #include "content/public/common/url_constants.h" @@ -17,15 +23,17 @@ namespace ash { -DemoModeAppUIConfig::DemoModeAppUIConfig() - : content::WebUIConfig(content::kChromeUIScheme, kChromeUIDemoModeAppHost) { -} +DemoModeAppUIConfig::DemoModeAppUIConfig( + base::RepeatingCallback component_path_producer) + : content::WebUIConfig(content::kChromeUIScheme, kChromeUIDemoModeAppHost), + component_path_producer_(std::move(component_path_producer)) {} DemoModeAppUIConfig::~DemoModeAppUIConfig() = default; std::unique_ptr DemoModeAppUIConfig::CreateWebUIController(content::WebUI* web_ui) { - return std::make_unique(web_ui); + return std::make_unique(web_ui, + component_path_producer_.Run()); } bool DemoModeAppUIConfig::IsWebUIEnabled( @@ -33,20 +41,68 @@ bool DemoModeAppUIConfig::IsWebUIEnabled( return ash::features::IsDemoModeSWAEnabled(); } -DemoModeAppUI::DemoModeAppUI(content::WebUI* web_ui) +scoped_refptr ReadFile( + const base::FilePath& absolute_resource_path) { + std::string data; + base::ReadFileToString(absolute_resource_path, &data); + return base::RefCountedString::TakeString(&data); +} + +bool ShouldSourceFromComponent( + const base::flat_set& webui_resource_paths, + const std::string& path) { + // TODO(b/232945108): Consider changing this logic to check if the absolute + // path exists in the component. This would still allow us show the default + // WebUI resource if the requested path isn't found. + return !webui_resource_paths.contains(path); +} + +void DemoModeAppUI::SourceDataFromComponent( + const base::FilePath& component_path, + const std::string& resource_path, + content::WebUIDataSource::GotDataCallback callback) { + // Convert to GURL to strip out query params and URL fragments + // + // TODO (b/234170189): Verify that query params won't be used in the prod Demo + // App, or add support for them here instead of ignoring them. + GURL full_url = GURL(kChromeUIDemoModeAppURL + resource_path); + // Trim leading slash from path + std::string path = full_url.path().substr(1); + + base::FilePath absolute_resource_path = component_path.AppendASCII(path); + + base::ThreadPool::PostTaskAndReplyWithResult( + FROM_HERE, {base::MayBlock()}, + base::BindOnce(&ReadFile, absolute_resource_path), std::move(callback)); +} + +DemoModeAppUI::DemoModeAppUI(content::WebUI* web_ui, + base::FilePath component_path) : ui::MojoWebUIController(web_ui) { - content::WebUIDataSource* html_source = + // We tack the resource path onto this component path, so CHECK that it's + // absolute so ".." parent references can't be used as an exploit + DCHECK(component_path.IsAbsolute()); + content::WebUIDataSource* data_source = content::WebUIDataSource::CreateAndAdd( web_ui->GetWebContents()->GetBrowserContext(), kChromeUIDemoModeAppHost); + base::flat_set webui_resource_paths; // Add required resources. for (size_t i = 0; i < kAshDemoModeAppResourcesSize; ++i) { - html_source->AddResourcePath(kAshDemoModeAppResources[i].path, + data_source->AddResourcePath(kAshDemoModeAppResources[i].path, kAshDemoModeAppResources[i].id); + webui_resource_paths.insert(kAshDemoModeAppResources[i].path); } - html_source->SetDefaultResource(IDR_ASH_DEMO_MODE_APP_DEMO_MODE_APP_HTML); + data_source->SetDefaultResource(IDR_ASH_DEMO_MODE_APP_DEMO_MODE_APP_HTML); + // Add empty string so default resource is still shown for + // chrome://demo-mode-app + webui_resource_paths.insert(""); + + data_source->SetRequestFilter( + base::BindRepeating(&ShouldSourceFromComponent, webui_resource_paths), + base::BindRepeating(&SourceDataFromComponent, component_path)); } DemoModeAppUI::~DemoModeAppUI() = default; diff --git a/ash/webui/demo_mode_app_ui/demo_mode_app_ui.h b/ash/webui/demo_mode_app_ui/demo_mode_app_ui.h index 4fe4d81301c11d..f69ce29f992b75 100644 --- a/ash/webui/demo_mode_app_ui/demo_mode_app_ui.h +++ b/ash/webui/demo_mode_app_ui/demo_mode_app_ui.h @@ -6,6 +6,8 @@ #define ASH_WEBUI_DEMO_MODE_APP_UI_DEMO_MODE_APP_UI_H_ #include "ash/webui/demo_mode_app_ui/mojom/demo_mode_app_ui.mojom.h" +#include "base/files/file_path.h" +#include "content/public/browser/web_ui_data_source.h" #include "content/public/browser/webui_config.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/pending_remote.h" @@ -16,20 +18,32 @@ namespace ash { class DemoModeAppUIConfig : public content::WebUIConfig { public: - DemoModeAppUIConfig(); + explicit DemoModeAppUIConfig( + base::RepeatingCallback component_path_producer); ~DemoModeAppUIConfig() override; std::unique_ptr CreateWebUIController( content::WebUI* web_ui) override; bool IsWebUIEnabled(content::BrowserContext* browser_context) override; + + private: + // Callback that provides the demo app component path to the WebUI controller. + // The path can't be passed directly into the DemoModeAppUIConfig constructor + // because the config is created during startup, whereas the component isn't + // loaded until the active demo session has started + // + // TODO(b/234174220): Consider creating a Delegate class that provides the + // component path instead + base::RepeatingCallback component_path_producer_; }; // The WebUI for chrome://demo-mode-app class DemoModeAppUI : public ui::MojoWebUIController, public mojom::demo_mode::PageHandlerFactory { public: - explicit DemoModeAppUI(content::WebUI* web_ui); + explicit DemoModeAppUI(content::WebUI* web_ui, + base::FilePath component_base_path); ~DemoModeAppUI() override; DemoModeAppUI(const DemoModeAppUI&) = delete; @@ -38,6 +52,12 @@ class DemoModeAppUI : public ui::MojoWebUIController, void BindInterface( mojo::PendingReceiver factory); + // Visible for testing + static void SourceDataFromComponent( + const base::FilePath& component_path, + const std::string& resource_path, + content::WebUIDataSource::GotDataCallback callback); + private: // mojom::DemoModePageHandlerFactory void CreatePageHandler( diff --git a/ash/webui/demo_mode_app_ui/demo_mode_app_ui_unittests.cc b/ash/webui/demo_mode_app_ui/demo_mode_app_ui_unittests.cc new file mode 100644 index 00000000000000..992fcf66bc6740 --- /dev/null +++ b/ash/webui/demo_mode_app_ui/demo_mode_app_ui_unittests.cc @@ -0,0 +1,112 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include + +#include "ash/webui/demo_mode_app_ui/demo_mode_app_ui.h" +#include "base/callback.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/ref_counted_memory.h" +#include "base/strings/strcat.h" +#include "base/test/task_environment.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/url_util.h" + +namespace ash { +namespace { + +const std::string kFileContents = "Test File Contents"; + +class DemoModeAppUITest : public testing::Test { + protected: + DemoModeAppUITest() = default; + ~DemoModeAppUITest() override = default; + + void SetUp() override { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + base::File file = base::CreateAndOpenTemporaryFileInDir( + temp_dir_.GetPath(), &content_file_path_); + base::WriteFile(content_file_path_, kFileContents); + + scheme_registry_ = std::make_unique(); + url::AddStandardScheme("chrome", url::SCHEME_WITH_HOST); + } + + base::FilePath content_file_path_; + base::ScopedTempDir temp_dir_; + std::unique_ptr scheme_registry_; + base::test::TaskEnvironment task_environment_; +}; + +void VerifyDataResponse(std::string expected_response, + base::OnceClosure quit_closure, + scoped_refptr data_response) { + std::string result(data_response->front_as(), data_response->size()); + EXPECT_EQ(result, expected_response); + std::move(quit_closure).Run(); +} + +TEST_F(DemoModeAppUITest, SourceDataFromComponent) { + base::RunLoop run_loop; + DemoModeAppUI::SourceDataFromComponent( + temp_dir_.GetPath(), content_file_path_.BaseName().MaybeAsASCII(), + base::BindOnce(&VerifyDataResponse, kFileContents, + run_loop.QuitClosure())); + run_loop.Run(); +} + +TEST_F(DemoModeAppUITest, SourceDataFromComponentQueryParam) { + base::RunLoop run_loop; + std::string resource_path_with_query_param = + content_file_path_.BaseName().MaybeAsASCII() + "?testparam=testvalue"; + + DemoModeAppUI::SourceDataFromComponent( + temp_dir_.GetPath(), resource_path_with_query_param, + base::BindOnce(&VerifyDataResponse, kFileContents, + run_loop.QuitClosure())); + run_loop.Run(); +} + +TEST_F(DemoModeAppUITest, SourceDataFromComponentURLFragment) { + base::RunLoop run_loop; + std::string resource_path_with_url_fragment = + content_file_path_.BaseName().MaybeAsASCII() + "#frag"; + + DemoModeAppUI::SourceDataFromComponent( + temp_dir_.GetPath(), resource_path_with_url_fragment, + base::BindOnce(&VerifyDataResponse, kFileContents, + run_loop.QuitClosure())); + run_loop.Run(); +} + +TEST_F(DemoModeAppUITest, SourceDataFromComponentQueryParamAndURLFragment) { + base::RunLoop run_loop; + std::string resource_path_with_url_fragment = + content_file_path_.BaseName().MaybeAsASCII() + + "?testparam=testvalue#frag"; + + DemoModeAppUI::SourceDataFromComponent( + temp_dir_.GetPath(), resource_path_with_url_fragment, + base::BindOnce(&VerifyDataResponse, kFileContents, + run_loop.QuitClosure())); + run_loop.Run(); +} + +TEST_F(DemoModeAppUITest, SourceDataFromComponentParentDirReference) { + base::RunLoop run_loop; + // Treat temp_dir_ as the parent of the component directory here, that + // a malicious ".."-containing path may be trying to access + base::ScopedTempDir component_dir; + ASSERT_TRUE(component_dir.CreateUniqueTempDirUnderPath(temp_dir_.GetPath())); + std::string resource_path_with_parent_ref = + "../" + content_file_path_.BaseName().MaybeAsASCII(); + + DemoModeAppUI::SourceDataFromComponent( + component_dir.GetPath(), resource_path_with_parent_ref, + base::BindOnce(&VerifyDataResponse, "", run_loop.QuitClosure())); + run_loop.Run(); +} + +} // namespace +} // namespace ash \ No newline at end of file diff --git a/ash/webui/demo_mode_app_ui/test/DEPS b/ash/webui/demo_mode_app_ui/test/DEPS deleted file mode 100644 index ac5677f9e5dc99..00000000000000 --- a/ash/webui/demo_mode_app_ui/test/DEPS +++ /dev/null @@ -1,4 +0,0 @@ -include_rules = [ - # Tests run in browser_tests, so can access things under chrome/test/base*. - "+chrome/test/base", -] diff --git a/ash/webui/demo_mode_app_ui/test/demo_mode_app_ui_browsertest.js b/ash/webui/demo_mode_app_ui/test/demo_mode_app_ui_browsertest.js deleted file mode 100644 index 7e3b34ba245a01..00000000000000 --- a/ash/webui/demo_mode_app_ui/test/demo_mode_app_ui_browsertest.js +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -GEN('#include "ash/constants/ash_features.h"'); -GEN('#include "content/public/test/browser_test.h"'); - -const HOST_ORIGIN = 'chrome://demo-mode-app'; - -// js2gtest fixtures require var here (https://crbug.com/1033337). -// eslint-disable-next-line no-var -var DemoModeAppUIBrowserTest = class extends testing.Test { - /** @override */ - get browsePreload() { - return HOST_ORIGIN; - } - - /** @override */ - get featureList() { - return {enabled: ['ash::features::kDemoModeSWA']}; - } -}; - -// Tests that chrome://demo-mode-app runs js file and that it goes -// somewhere instead of 404ing or crashing. -TEST_F('DemoModeAppUIBrowserTest', 'HasChromeSchemeURL', () => { - const header = document.querySelector('h1'); - - assertEquals(header.innerText, 'Demo Mode App'); - assertEquals(document.location.origin, HOST_ORIGIN); -}); diff --git a/ash/webui/demo_mode_app_ui/url_constants.cc b/ash/webui/demo_mode_app_ui/url_constants.cc index 5bfc317bc412f9..66da17e96dcd4e 100644 --- a/ash/webui/demo_mode_app_ui/url_constants.cc +++ b/ash/webui/demo_mode_app_ui/url_constants.cc @@ -8,5 +8,6 @@ namespace ash { const char kChromeUIDemoModeAppHost[] = "demo-mode-app"; const char kChromeUIDemoModeAppURL[] = "chrome://demo-mode-app/"; +// TODO(b/232019361): Add untrusted demo mode app url to constants here } // namespace ash diff --git a/chrome/browser/ash/login/demo_mode/demo_session.cc b/chrome/browser/ash/login/demo_mode/demo_session.cc index 2200dab7652599..1a09ade4f60ed8 100644 --- a/chrome/browser/ash/login/demo_mode/demo_session.cc +++ b/chrome/browser/ash/login/demo_mode/demo_session.cc @@ -8,6 +8,7 @@ #include #include "ash/components/tpm/install_attributes.h" +#include "ash/constants/ash_features.h" #include "ash/constants/ash_switches.h" #include "ash/public/cpp/locale_update_controller.h" #include "base/bind.h" @@ -550,6 +551,16 @@ void DemoSession::OnSessionStateChanged() { InstallAppFromUpdateUrl(GetHighlightsAppId()); + // Download/update the Demo app component during session startup + if (features::IsDemoModeSWAEnabled()) { + g_browser_process->platform_part()->cros_component_manager()->Load( + "demo-mode-app", + component_updater::CrOSComponentManager::MountPolicy::kMount, + component_updater::CrOSComponentManager::UpdatePolicy::kForce, + base::BindOnce(&DemoSession::OnDemoAppComponentLoaded, + weak_ptr_factory_.GetWeakPtr())); + } + EnsureResourcesLoaded(base::BindOnce(&DemoSession::InstallDemoResources, weak_ptr_factory_.GetWeakPtr())); break; @@ -558,6 +569,19 @@ void DemoSession::OnSessionStateChanged() { } } +// TODO(b/231761044): Launch the Demo Mode SWA after the component has +// been successfully loaded +void DemoSession::OnDemoAppComponentLoaded( + component_updater::CrOSComponentManager::Error error, + const base::FilePath& path) { + if (error != component_updater::CrOSComponentManager::Error::NONE) { + LOG(WARNING) << "Error loading demo mode app component: " + << static_cast(error); + return; + } + demo_app_component_path_ = path; +} + void DemoSession::ShowSplashScreen() { const std::string current_locale = g_browser_process->GetApplicationLocale(); base::FilePath image_path = demo_resources_->path() diff --git a/chrome/browser/ash/login/demo_mode/demo_session.h b/chrome/browser/ash/login/demo_mode/demo_session.h index c0366b6cfce7f1..2c6c3d5e218151 100644 --- a/chrome/browser/ash/login/demo_mode/demo_session.h +++ b/chrome/browser/ash/login/demo_mode/demo_session.h @@ -15,6 +15,7 @@ #include "base/scoped_multi_source_observation.h" #include "base/scoped_observation.h" #include "chrome/browser/ash/login/demo_mode/demo_extensions_external_loader.h" +#include "chrome/browser/component_updater/cros_component_manager.h" #include "components/services/app_service/public/cpp/app_registry_cache.h" #include "components/session_manager/core/session_manager.h" #include "components/session_manager/core/session_manager_observer.h" @@ -173,12 +174,21 @@ class DemoSession : public session_manager::SessionManagerObserver, bool started() const { return started_; } + base::FilePath DemoAppComponentPath() { + DCHECK(!demo_app_component_path_.empty()); + return demo_app_component_path_; + } + const DemoResources* resources() const { return demo_resources_.get(); } private: DemoSession(); ~DemoSession() override; + void OnDemoAppComponentLoaded( + component_updater::CrOSComponentManager::Error error, + const base::FilePath& path); + // Get country code and full name in current language pair sorted by their // full name in currently selected language. static std::vector @@ -241,6 +251,8 @@ class DemoSession : public session_manager::SessionManagerObserver, bool splash_screen_removed_ = false; bool screensaver_activated_ = false; + base::FilePath demo_app_component_path_; + base::WeakPtrFactory weak_ptr_factory_{this}; }; diff --git a/chrome/browser/ash/web_applications/demo_mode_app_integration_browsertest.cc b/chrome/browser/ash/web_applications/demo_mode_app_integration_browsertest.cc index 9dc96a4d02a34f..f5c92ff4d11fb0 100644 --- a/chrome/browser/ash/web_applications/demo_mode_app_integration_browsertest.cc +++ b/chrome/browser/ash/web_applications/demo_mode_app_integration_browsertest.cc @@ -3,20 +3,49 @@ // found in the LICENSE file. #include "ash/constants/ash_features.h" +#include "ash/webui/demo_mode_app_ui/demo_mode_app_ui.h" #include "ash/webui/demo_mode_app_ui/url_constants.h" +#include "ash/webui/web_applications/test/sandboxed_web_ui_test_base.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" #include "base/scoped_observation.h" +#include "base/test/bind.h" #include "base/test/scoped_feature_list.h" +#include "chrome/browser/apps/app_service/app_launch_params.h" #include "chrome/browser/ash/web_applications/system_web_app_integration_test.h" +#include "content/public/browser/webui_config_map.h" #include "content/public/test/browser_test.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_observer.h" +const char kTestHtml[] = + "" + " Hello World!" + "" + "" + "

browsertest

" + ""; + +const char kEmptyHtml[] = ""; + class DemoModeAppIntegrationTest : public SystemWebAppIntegrationTest { public: DemoModeAppIntegrationTest() { scoped_feature_list_.InitAndEnableFeature(chromeos::features::kDemoModeSWA); } - private: + + protected: + void SetUpOnMainThread() override { + base::ScopedAllowBlockingForTesting allow_blocking; + ASSERT_TRUE(component_dir_.CreateUniqueTempDir()); + content::WebUIConfigMap::GetInstance().RemoveForTesting( + url::Origin::Create(GURL(ash::kChromeUIDemoModeAppURL))); + content::WebUIConfigMap::GetInstance().AddWebUIConfig( + std::make_unique(base::BindLambdaForTesting( + [&] { return component_dir_.GetPath(); }))); + } + + base::ScopedTempDir component_dir_; base::test::ScopedFeatureList scoped_feature_list_; }; @@ -77,5 +106,42 @@ IN_PROC_BROWSER_TEST_P(DemoModeAppIntegrationTest, WidgetFullscreenWaiter(widget).WaitThenAssert(false); } +IN_PROC_BROWSER_TEST_P(DemoModeAppIntegrationTest, + DemoModeAppLoadComponentContent) { + base::ScopedAllowBlockingForTesting allow_blocking; + base::FilePath file_path = component_dir_.GetPath().AppendASCII("test.html"); + base::WriteFile(file_path, kTestHtml); + + WaitForTestSystemAppInstall(); + + apps::AppLaunchParams params = + LaunchParamsForApp(ash::SystemWebAppType::DEMO_MODE); + params.override_url = + GURL("chrome://demo-mode-app/" + file_path.BaseName().MaybeAsASCII()); + content::WebContents* web_contents = LaunchApp(std::move(params)); + + EXPECT_EQ( + std::string(kTestHtml), + content::EvalJs(web_contents, R"(document.documentElement.innerHTML)", + content::EXECUTE_SCRIPT_DEFAULT_OPTIONS, 1)); +} + +// TODO(b/232945108): Change this to instead verify default resource if +// ShouldSourceFromComponent logic is changed to check if path exists +IN_PROC_BROWSER_TEST_P(DemoModeAppIntegrationTest, + DemoModeAppNonexistentPathRendersEmptyPage) { + WaitForTestSystemAppInstall(); + + apps::AppLaunchParams params = + LaunchParamsForApp(ash::SystemWebAppType::DEMO_MODE); + params.override_url = GURL("chrome://demo-mode-app/nonexistent.html"); + content::WebContents* web_contents = LaunchApp(std::move(params)); + + EXPECT_EQ( + std::string(kEmptyHtml), + content::EvalJs(web_contents, R"(document.documentElement.innerHTML)", + content::EXECUTE_SCRIPT_DEFAULT_OPTIONS, 1)); +} + INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_GUEST_SESSION_P( DemoModeAppIntegrationTest); diff --git a/chrome/browser/ui/webui/chrome_web_ui_configs_chromeos.cc b/chrome/browser/ui/webui/chrome_web_ui_configs_chromeos.cc index e97fe32d45f20a..54b9128e68df81 100644 --- a/chrome/browser/ui/webui/chrome_web_ui_configs_chromeos.cc +++ b/chrome/browser/ui/webui/chrome_web_ui_configs_chromeos.cc @@ -5,6 +5,7 @@ #include "chrome/browser/ui/webui/chrome_untrusted_web_ui_configs_chromeos.h" #include "build/chromeos_buildflags.h" +#include "chrome/browser/ash/login/demo_mode/demo_session.h" #include "content/public/browser/webui_config_map.h" #if BUILDFLAG(IS_CHROMEOS_ASH) @@ -21,7 +22,9 @@ void RegisterAshChromeWebUIConfigs() { #if !defined(OFFICIAL_BUILD) auto& map = content::WebUIConfigMap::GetInstance(); map.AddWebUIConfig(std::make_unique()); - map.AddWebUIConfig(std::make_unique()); + map.AddWebUIConfig( + std::make_unique(base::BindRepeating( + [] { return ash::DemoSession::Get()->DemoAppComponentPath(); }))); #endif // !defined(OFFICIAL_BUILD) } #endif // BUILDFLAG(IS_CHROMEOS_ASH) diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 2c74a5c7dfcaf4..1dc6dceff7c650 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn @@ -2658,10 +2658,7 @@ if (!is_android) { data_deps += [ "//chrome/test/data/webui:browser_tests_js_webui" ] if (!is_official_build) { - deps += [ - "//ash/webui/demo_mode_app_ui:browser_tests_js", - "//ash/webui/sample_system_web_app_ui:browser_tests_js", - ] + deps += [ "//ash/webui/sample_system_web_app_ui:browser_tests_js" ] } } }