Web UI: Add chrome://test data source for testing JS modules
- Add a test data source that serves files from chrome://test
- Autogenerate HTML responses containing a single
<script type="module"> with src set to a requested JS file at runtime.
Request these responses by preloading
chrome://test?module=JS_TEST_FILE.js
- Remaining dependencies, other than mocha and mocha adapter, can
be imported via the JS test module, instead of using |extraLibraries|.
- Map requests for chrome://test URLs to the appropriate Web UI
controller using a new webuiHost parameter for tests.
Bug: 968804
Change-Id: I5b409bb54da5611be68fe590176edec3c7b668e2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1700294
Commit-Queue: Rebekah Potter <rbpotter@chromium.org>
Reviewed-by: Demetrios Papadopoulos <dpapad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#678925}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 555d5e4e..b4dbfa5 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -5638,6 +5638,8 @@
"signin/token_revoker_test_utils.h",
"ui/webui/signin/login_ui_test_utils.cc",
"ui/webui/signin/login_ui_test_utils.h",
+ "ui/webui/test_data_source.cc",
+ "ui/webui/test_data_source.h",
"ui/webui/web_ui_test_handler.cc",
"ui/webui/web_ui_test_handler.h",
]
diff --git a/chrome/browser/ui/webui/test_data_source.cc b/chrome/browser/ui/webui/test_data_source.cc
new file mode 100644
index 0000000..b857b4f
--- /dev/null
+++ b/chrome/browser/ui/webui/test_data_source.cc
@@ -0,0 +1,103 @@
+// Copyright 2019 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 "chrome/browser/ui/webui/test_data_source.h"
+
+#include <memory>
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/common/webui_url_constants.h"
+#include "content/public/browser/url_data_source.h"
+#include "content/public/common/url_constants.h"
+
+namespace {
+const char kModuleQuery[] = "module=";
+} // namespace
+
+std::string TestDataSource::GetSource() {
+ return "test";
+}
+
+void TestDataSource::StartDataRequest(
+ const std::string& path,
+ const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
+ const content::URLDataSource::GotDataCallback& callback) {
+ base::PostTaskWithTraits(
+ FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
+ base::BindOnce(&TestDataSource::ReadFile, base::Unretained(this), path,
+ callback));
+}
+
+std::string TestDataSource::GetMimeType(const std::string& path) {
+ if (base::EndsWith(path, ".html", base::CompareCase::INSENSITIVE_ASCII) ||
+ base::StartsWith(GetURLForPath(path).query(), kModuleQuery,
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ // Direct request for HTML, or autogenerated HTML response for module query.
+ return "text/html";
+ }
+ // The test data source currently only serves HTML and JS.
+ CHECK(base::EndsWith(path, ".js", base::CompareCase::INSENSITIVE_ASCII));
+ return "application/javascript";
+}
+
+bool TestDataSource::ShouldServeMimeTypeAsContentTypeHeader() {
+ return true;
+}
+
+bool TestDataSource::AllowCaching() {
+ return false;
+}
+
+std::string TestDataSource::GetContentSecurityPolicyScriptSrc() {
+ return "script-src chrome://* 'self';";
+}
+
+GURL TestDataSource::GetURLForPath(const std::string& path) {
+ return GURL(std::string(content::kChromeUIScheme) + "://" + GetSource() +
+ "/" + path);
+}
+
+void TestDataSource::ReadFile(
+ const std::string& path,
+ const content::URLDataSource::GotDataCallback& callback) {
+ if (test_data_.empty()) {
+ CHECK(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_));
+ CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &source_root_));
+ }
+ base::FilePath root = test_data_.Append(FILE_PATH_LITERAL("webui"));
+ std::string content;
+
+ GURL url = GetURLForPath(path);
+ CHECK(url.is_valid());
+ if (base::StartsWith(url.query(), kModuleQuery,
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ std::string js_path = url.query().substr(strlen(kModuleQuery));
+ base::FilePath file_path =
+ root.Append(base::FilePath::FromUTF8Unsafe(js_path));
+ // Do some basic validation of the JS file path provided in the query.
+ CHECK_EQ(file_path.Extension(), FILE_PATH_LITERAL(".js"));
+ CHECK(base::PathExists(file_path))
+ << url.spec() << "=" << file_path.value();
+ content = "<script type=\"module\" src=\"" + js_path + "\"></script>";
+ } else {
+ base::FilePath file_path =
+ root.Append(base::FilePath::FromUTF8Unsafe(path));
+ CHECK(base::ReadFileToString(file_path, &content))
+ << url.spec() << "=" << file_path.value();
+ }
+
+ scoped_refptr<base::RefCountedString> response =
+ base::RefCountedString::TakeString(&content);
+ callback.Run(response.get());
+}
diff --git a/chrome/browser/ui/webui/test_data_source.h b/chrome/browser/ui/webui/test_data_source.h
new file mode 100644
index 0000000..680979c
--- /dev/null
+++ b/chrome/browser/ui/webui/test_data_source.h
@@ -0,0 +1,48 @@
+// Copyright 2019 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.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_TEST_DATA_SOURCE_H_
+#define CHROME_BROWSER_UI_WEBUI_TEST_DATA_SOURCE_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "content/public/browser/url_data_source.h"
+#include "url/gurl.h"
+
+// Serves files at chrome://test/ from //src/chrome/test/data/webui.
+class TestDataSource : public content::URLDataSource {
+ public:
+ TestDataSource() = default;
+ ~TestDataSource() override = default;
+
+ private:
+ void StartDataRequest(
+ const std::string& path,
+ const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
+ const content::URLDataSource::GotDataCallback& callback) override;
+
+ std::string GetMimeType(const std::string& path) override;
+
+ bool ShouldServeMimeTypeAsContentTypeHeader() override;
+
+ bool AllowCaching() override;
+
+ std::string GetSource() override;
+
+ std::string GetContentSecurityPolicyScriptSrc() override;
+
+ GURL GetURLForPath(const std::string& path);
+
+ void ReadFile(const std::string& path,
+ const content::URLDataSource::GotDataCallback& callback);
+
+ base::FilePath test_data_;
+ base::FilePath source_root_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestDataSource);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_TEST_DATA_SOURCE_H_
diff --git a/chrome/test/base/js2gtest.js b/chrome/test/base/js2gtest.js
index aafa58a4..d66169a 100644
--- a/chrome/test/base/js2gtest.js
+++ b/chrome/test/base/js2gtest.js
@@ -386,6 +386,7 @@
var testShouldFail = this[testFixture].prototype.testShouldFail;
var testPredicate = testShouldFail ? 'ASSERT_FALSE' : 'ASSERT_TRUE';
var loaderFile = this[testFixture].prototype.loaderFile;
+ var webuiHost = this[testFixture].prototype.webuiHost;
var extraLibraries = genIncludes.concat(
this[testFixture].prototype.extraLibraries.map(includeFileToPath),
resolveClosureModuleDeps(this[testFixture].prototype.closureModuleDeps),
@@ -529,6 +530,10 @@
output(`
set_loader_file("${loaderFile}");`);
}
+ if (webuiHost) {
+ output(`
+ set_webui_host("${webuiHost}");`);
+ }
if (testGenPreamble)
testGenPreamble(testFixture, testFunction);
if (browsePreload)
diff --git a/chrome/test/base/test_chrome_web_ui_controller_factory.cc b/chrome/test/base/test_chrome_web_ui_controller_factory.cc
index 3fd0787..9f9d7b64 100644
--- a/chrome/test/base/test_chrome_web_ui_controller_factory.cc
+++ b/chrome/test/base/test_chrome_web_ui_controller_factory.cc
@@ -5,6 +5,8 @@
#include "chrome/test/base/test_chrome_web_ui_controller_factory.h"
#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/test_data_source.h"
+#include "content/public/browser/url_data_source.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui_controller.h"
@@ -21,6 +23,11 @@
TestChromeWebUIControllerFactory::~TestChromeWebUIControllerFactory() {
}
+void TestChromeWebUIControllerFactory::set_webui_host(
+ const std::string& webui_host) {
+ webui_host_ = webui_host;
+}
+
void TestChromeWebUIControllerFactory::AddFactoryOverride(
const std::string& host, WebUIProvider* provider) {
DCHECK_EQ(0U, factory_overrides_.count(host));
@@ -37,9 +44,11 @@
content::BrowserContext* browser_context,
const GURL& url) {
Profile* profile = Profile::FromBrowserContext(browser_context);
- WebUIProvider* provider = GetWebUIProvider(profile, url);
- return provider ? reinterpret_cast<WebUI::TypeID>(provider) :
- ChromeWebUIControllerFactory::GetWebUIType(profile, url);
+ const GURL& webui_url = TestURLToWebUIURL(url);
+ WebUIProvider* provider = GetWebUIProvider(profile, webui_url);
+ return provider
+ ? reinterpret_cast<WebUI::TypeID>(provider)
+ : ChromeWebUIControllerFactory::GetWebUIType(profile, webui_url);
}
std::unique_ptr<WebUIController>
@@ -47,15 +56,31 @@
content::WebUI* web_ui,
const GURL& url) {
Profile* profile = Profile::FromWebUI(web_ui);
- WebUIProvider* provider = GetWebUIProvider(profile, url);
- return provider ? provider->NewWebUI(web_ui, url)
- : ChromeWebUIControllerFactory::CreateWebUIControllerForURL(
- web_ui, url);
+ const GURL& webui_url = TestURLToWebUIURL(url);
+ WebUIProvider* provider = GetWebUIProvider(profile, webui_url);
+ auto controller =
+ provider ? provider->NewWebUI(web_ui, webui_url)
+ : ChromeWebUIControllerFactory::CreateWebUIControllerForURL(
+ web_ui, webui_url);
+ content::URLDataSource::Add(profile, std::make_unique<TestDataSource>());
+ return controller;
}
TestChromeWebUIControllerFactory::WebUIProvider*
TestChromeWebUIControllerFactory::GetWebUIProvider(
Profile* profile, const GURL& url) const {
- auto found = factory_overrides_.find(url.host());
+ const GURL& webui_url = TestURLToWebUIURL(url);
+ auto found = factory_overrides_.find(webui_url.host());
return found != factory_overrides_.end() ? found->second : nullptr;
}
+
+GURL TestChromeWebUIControllerFactory::TestURLToWebUIURL(
+ const GURL& url) const {
+ if (url.host() != "test" || webui_host_.empty())
+ return url;
+
+ GURL webui_url(url);
+ GURL::Replacements replacements;
+ replacements.SetHostStr(webui_host_);
+ return webui_url.ReplaceComponents(replacements);
+}
diff --git a/chrome/test/base/test_chrome_web_ui_controller_factory.h b/chrome/test/base/test_chrome_web_ui_controller_factory.h
index f1da9a30..f69fd57 100644
--- a/chrome/test/base/test_chrome_web_ui_controller_factory.h
+++ b/chrome/test/base/test_chrome_web_ui_controller_factory.h
@@ -36,6 +36,9 @@
TestChromeWebUIControllerFactory();
~TestChromeWebUIControllerFactory() override;
+ // Sets the Web UI host.
+ void set_webui_host(const std::string& webui_host);
+
// Override the creation for urls having |host| with |provider|.
void AddFactoryOverride(const std::string& host, WebUIProvider* provider);
@@ -53,9 +56,19 @@
// Return the WebUIProvider for the |url|'s host if it exists, otherwise NULL.
WebUIProvider* GetWebUIProvider(Profile* profile, const GURL& url) const;
+ // Replace |url|'s host with the Web UI host if |url| is a test URL served
+ // from the TestDataSource. This ensures the factory always creates the
+ // appropriate Web UI controller when these URLs are encountered instead of
+ // failing.
+ GURL TestURLToWebUIURL(const GURL& url) const;
+
// Stores the mapping of host to WebUIProvider.
FactoryOverridesMap factory_overrides_;
+ // Stores the Web UI host to create the correct Web UI controller for
+ // chrome://test URL requests.
+ std::string webui_host_;
+
DISALLOW_COPY_AND_ASSIGN(TestChromeWebUIControllerFactory);
};
diff --git a/chrome/test/base/web_ui_browser_test.cc b/chrome/test/base/web_ui_browser_test.cc
index 5f476f3..9b35a89 100644
--- a/chrome/test/base/web_ui_browser_test.cc
+++ b/chrome/test/base/web_ui_browser_test.cc
@@ -375,6 +375,10 @@
loader_file_ = loader_file;
}
+void BaseWebUIBrowserTest::set_webui_host(const std::string& webui_host) {
+ test_factory_->set_webui_host(webui_host);
+}
+
namespace {
// DataSource for the dummy URL. If no data source is provided then an error
diff --git a/chrome/test/base/web_ui_browser_test.h b/chrome/test/base/web_ui_browser_test.h
index af987c7..cd7f0805 100644
--- a/chrome/test/base/web_ui_browser_test.h
+++ b/chrome/test/base/web_ui_browser_test.h
@@ -5,7 +5,9 @@
#ifndef CHROME_TEST_BASE_WEB_UI_BROWSER_TEST_H_
#define CHROME_TEST_BASE_WEB_UI_BROWSER_TEST_H_
+#include <memory>
#include <string>
+#include <utility>
#include <vector>
#include "base/files/file_path.h"
@@ -108,6 +110,7 @@
void set_preload_test_name(const std::string& preload_test_name);
void set_loader_file(const std::string& loader_file);
+ void set_webui_host(const std::string& webui_host);
// Enable command line flags for test.
void SetUpCommandLine(base::CommandLine* command_line) override;