blob: 3ef4bea614c789d0eddd14d43e4a8226b7ecac99 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/webui/web_applications/test/sandboxed_web_ui_test_base.h"
#include "base/memory/raw_ptr.h"
#include <vector>
#include "ash/webui/web_applications/webui_test_prod_util.h"
#include "base/base_paths.h"
#include "base/containers/contains.h"
#include "base/files/file_util.h"
#include "base/memory/ref_counted_memory.h"
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/test/base/js_test_api.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/test/test_navigation_observer.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
void HandleTestFileRequestCallback(
const base::FilePath& test_file_location,
const std::string& path,
content::WebUIDataSource::GotDataCallback callback) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath source_root;
base::PathService::Get(base::DIR_SOURCE_ROOT, &source_root);
const base::FilePath test_file_path =
source_root.Append(test_file_location).AppendASCII(path);
std::string contents;
CHECK(base::ReadFileToString(test_file_path, &contents)) << test_file_path;
std::move(callback).Run(
base::MakeRefCounted<base::RefCountedString>(std::move(contents)));
}
bool TestRequestHandlerShouldHandleRequest(
const std::vector<std::string>& test_paths,
const std::string& path) {
return base::Contains(test_paths, path);
}
std::string DefaultScriptTimeoutLog(const std::string& script,
const base::TimeDelta& timeout) {
return base::StringPrintf("Hit timeout of %fs:\n", timeout.InSecondsF()) +
script;
}
} // namespace
class SandboxedWebUiAppTestBase::TestCodeInjector
: public content::TestNavigationObserver {
public:
explicit TestCodeInjector(SandboxedWebUiAppTestBase* owner)
: TestNavigationObserver(GURL(owner->host_url_)), owner_(owner) {
WatchExistingWebContents();
StartWatchingNewWebContents();
}
// TestNavigationObserver:
void OnDidFinishNavigation(
content::NavigationHandle* navigation_handle) override {
if (navigation_handle->GetURL().DeprecatedGetOriginAsURL() !=
GURL(owner_->sandboxed_url_))
return;
auto* guest_frame = navigation_handle->GetRenderFrameHost();
// Inject the JS Test API first (assertEquals, etc).
std::vector<base::FilePath> scripts = JsTestApiConfig().default_libraries;
scripts.insert(scripts.end(), owner_->scripts_.begin(),
owner_->scripts_.end());
for (const auto& script : scripts) {
// Use ExecuteScript(), not ExecJs(), because of Content Security Policy
// directive: "script-src chrome://resources 'self'"
ASSERT_TRUE(
content::ExecuteScript(guest_frame, LoadJsTestLibrary(script)));
}
if (!owner_->test_module_.empty()) {
constexpr char kScript[] = R"(
(() => {
const s = document.createElement('script');
s.type = 'module';
s.src = '$1';
document.body.appendChild(s);
})();
)";
ASSERT_TRUE(content::ExecuteScript(
guest_frame, base::ReplaceStringPlaceholders(
kScript, {owner_->test_module_}, nullptr)));
}
TestNavigationObserver::OnDidFinishNavigation(navigation_handle);
}
private:
const raw_ptr<SandboxedWebUiAppTestBase, ExperimentalAsh> owner_;
};
SandboxedWebUiAppTestBase::SandboxedWebUiAppTestBase(
const std::string& host_url,
const std::string& sandboxed_url,
const std::vector<base::FilePath>& scripts,
const std::string& test_module)
: host_url_(host_url),
sandboxed_url_(sandboxed_url),
scripts_(scripts),
test_module_(test_module) {}
SandboxedWebUiAppTestBase::~SandboxedWebUiAppTestBase() = default;
// static
void SandboxedWebUiAppTestBase::ConfigureDefaultTestRequestHandler(
const base::FilePath& root_folder,
const std::vector<std::string>& resource_files) {
SetTestableDataSourceRequestHandlerForTesting(
base::BindRepeating(&TestRequestHandlerShouldHandleRequest,
resource_files),
base::BindRepeating(&HandleTestFileRequestCallback, root_folder));
}
// static
std::string SandboxedWebUiAppTestBase::LoadJsTestLibrary(
const base::FilePath& script_path) {
base::FilePath source_root;
EXPECT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &source_root));
const auto full_script_path =
script_path.IsAbsolute() ? script_path : source_root.Append(script_path);
base::ScopedAllowBlockingForTesting allow_blocking;
std::string injected_content;
EXPECT_TRUE(base::ReadFileToString(full_script_path, &injected_content));
return injected_content;
}
// static
content::RenderFrameHost* SandboxedWebUiAppTestBase::GetAppFrame(
content::WebContents* web_ui) {
// Assume the first subframe is the app.
content::RenderFrameHost* subframe = ChildFrameAt(web_ui, 0);
EXPECT_TRUE(subframe) << web_ui->GetVisibleURL();
return subframe;
}
// static
content::EvalJsResult SandboxedWebUiAppTestBase::EvalJsInAppFrame(
content::WebContents* web_ui,
const std::string& script) {
// Clients of this helper all run in the same isolated world.
constexpr int kWorldId = 1;
base::TimeDelta script_timeout = TestTimeouts::action_timeout();
base::test::ScopedRunLoopTimeout scoped_run_timeout(
FROM_HERE, script_timeout,
base::BindRepeating(&DefaultScriptTimeoutLog, script, script_timeout));
return EvalJs(GetAppFrame(web_ui), script,
content::EXECUTE_SCRIPT_DEFAULT_OPTIONS, kWorldId);
}
void SandboxedWebUiAppTestBase::SetUpOnMainThread() {
injector_ = std::make_unique<TestCodeInjector>(this);
MojoWebUIBrowserTest::SetUpOnMainThread();
}