blob: 0425adabad2fa2f78397a992ef1d09f674aed219 [file] [log] [blame]
// Copyright 2014 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 <limits>
#include <utility>
#include "base/callback.h"
#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/memory/ref_counted_memory.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "content/browser/webui/web_ui_controller_factory_registry.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_data_source.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/url_utils.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/data/web_ui_test_mojo_bindings.mojom.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "services/service_manager/public/cpp/binder_registry.h"
namespace content {
namespace {
bool g_got_message = false;
base::FilePath GetFilePathForJSResource(const std::string& path) {
base::ScopedAllowBlockingForTesting allow_blocking;
std::string binding_path = "gen/" + path;
#if defined(OS_WIN)
base::ReplaceChars(binding_path, "//", "\\", &binding_path);
#endif
base::FilePath exe_dir;
base::PathService::Get(base::DIR_EXE, &exe_dir);
return exe_dir.AppendASCII(binding_path);
}
// The bindings for the page are generated from a .mojom file. This code looks
// up the generated file from disk and returns it.
bool GetResource(const std::string& id,
const WebUIDataSource::GotDataCallback& callback) {
base::ScopedAllowBlockingForTesting allow_blocking;
std::string contents;
if (base::EndsWith(id, ".mojom.js", base::CompareCase::SENSITIVE)) {
CHECK(base::ReadFileToString(GetFilePathForJSResource(id), &contents))
<< id;
} else {
base::FilePath path;
CHECK(base::PathService::Get(content::DIR_TEST_DATA, &path));
path = path.AppendASCII(id.substr(0, id.find("?")));
CHECK(base::ReadFileToString(path, &contents)) << path.value();
}
base::RefCountedString* ref_contents = new base::RefCountedString;
ref_contents->data() = contents;
callback.Run(ref_contents);
return true;
}
class BrowserTargetImpl : public mojom::BrowserTarget {
public:
BrowserTargetImpl(base::RunLoop* run_loop,
mojo::InterfaceRequest<mojom::BrowserTarget> request)
: run_loop_(run_loop), binding_(this, std::move(request)) {}
~BrowserTargetImpl() override {}
// mojom::BrowserTarget overrides:
void Start(StartCallback closure) override { std::move(closure).Run(); }
void Stop() override {
g_got_message = true;
run_loop_->Quit();
}
protected:
base::RunLoop* const run_loop_;
private:
mojo::Binding<mojom::BrowserTarget> binding_;
DISALLOW_COPY_AND_ASSIGN(BrowserTargetImpl);
};
// WebUIController that sets up mojo bindings.
class TestWebUIController : public WebUIController {
public:
TestWebUIController(WebUI* web_ui,
base::RunLoop* run_loop,
int bindings = BINDINGS_POLICY_MOJO_WEB_UI)
: WebUIController(web_ui), run_loop_(run_loop) {
web_ui->SetBindings(bindings);
{
WebUIDataSource* data_source = WebUIDataSource::Create("mojo-web-ui");
data_source->SetRequestFilter(base::BindRepeating(&GetResource));
WebUIDataSource::Add(web_ui->GetWebContents()->GetBrowserContext(),
data_source);
}
{
WebUIDataSource* data_source = WebUIDataSource::Create("dummy-web-ui");
data_source->SetRequestFilter(base::BindRepeating(
[](const std::string& id,
const WebUIDataSource::GotDataCallback& callback) {
callback.Run(new base::RefCountedString);
return true;
}));
WebUIDataSource::Add(web_ui->GetWebContents()->GetBrowserContext(),
data_source);
}
}
protected:
base::RunLoop* const run_loop_;
std::unique_ptr<BrowserTargetImpl> browser_target_;
private:
DISALLOW_COPY_AND_ASSIGN(TestWebUIController);
};
// TestWebUIController that additionally creates the ping test BrowserTarget
// implementation at the right time.
class PingTestWebUIController : public TestWebUIController,
public WebContentsObserver {
public:
PingTestWebUIController(WebUI* web_ui, base::RunLoop* run_loop)
: TestWebUIController(web_ui, run_loop),
WebContentsObserver(web_ui->GetWebContents()) {
registry_.AddInterface(base::Bind(&PingTestWebUIController::CreateHandler,
base::Unretained(this)));
}
~PingTestWebUIController() override {}
// WebContentsObserver implementation:
void OnInterfaceRequestFromFrame(
RenderFrameHost* render_frame_host,
const std::string& interface_name,
mojo::ScopedMessagePipeHandle* interface_pipe) override {
registry_.TryBindInterface(interface_name, interface_pipe);
}
void CreateHandler(mojom::BrowserTargetRequest request) {
browser_target_ =
std::make_unique<BrowserTargetImpl>(run_loop_, std::move(request));
}
private:
service_manager::BinderRegistry registry_;
DISALLOW_COPY_AND_ASSIGN(PingTestWebUIController);
};
// WebUIControllerFactory that creates TestWebUIController.
class TestWebUIControllerFactory : public WebUIControllerFactory {
public:
TestWebUIControllerFactory()
: run_loop_(nullptr),
registered_controllers_(
{{"ping", base::BindRepeating(
&TestWebUIControllerFactory::CreatePingController,
base::Unretained(this))},
{"hybrid", base::BindRepeating(
&TestWebUIControllerFactory::CreateHybridController,
base::Unretained(this))},
{"webui_bindings",
base::BindRepeating(
&TestWebUIControllerFactory::CreateWebUIController,
base::Unretained(this))}}) {}
void set_run_loop(base::RunLoop* run_loop) { run_loop_ = run_loop; }
std::unique_ptr<WebUIController> CreateWebUIControllerForURL(
WebUI* web_ui,
const GURL& url) const override {
if (!web_ui_enabled_ || !url.SchemeIs(kChromeUIScheme))
return nullptr;
auto it = registered_controllers_.find(url.query());
if (it != registered_controllers_.end())
return it->second.Run(web_ui);
return std::make_unique<TestWebUIController>(web_ui, run_loop_);
}
WebUI::TypeID GetWebUIType(BrowserContext* browser_context,
const GURL& url) const 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) const override {
return GetWebUIType(browser_context, url) != WebUI::kNoWebUI;
}
bool UseWebUIBindingsForURL(BrowserContext* browser_context,
const GURL& url) const override {
return GetWebUIType(browser_context, url) != WebUI::kNoWebUI;
}
void set_web_ui_enabled(bool enabled) { web_ui_enabled_ = enabled; }
private:
std::unique_ptr<WebUIController> CreatePingController(WebUI* web_ui) {
return std::make_unique<PingTestWebUIController>(web_ui, run_loop_);
}
std::unique_ptr<WebUIController> CreateHybridController(WebUI* web_ui) {
return std::make_unique<TestWebUIController>(
web_ui, run_loop_,
BINDINGS_POLICY_WEB_UI | BINDINGS_POLICY_MOJO_WEB_UI);
}
std::unique_ptr<WebUIController> CreateWebUIController(WebUI* web_ui) {
return std::make_unique<TestWebUIController>(web_ui, run_loop_,
BINDINGS_POLICY_WEB_UI);
}
base::RunLoop* run_loop_;
bool web_ui_enabled_ = true;
const base::flat_map<
std::string,
base::RepeatingCallback<std::unique_ptr<WebUIController>(WebUI*)>>
registered_controllers_;
DISALLOW_COPY_AND_ASSIGN(TestWebUIControllerFactory);
};
class WebUIMojoTest : public ContentBrowserTest {
public:
WebUIMojoTest() {
WebUIControllerFactory::RegisterFactory(&factory_);
}
~WebUIMojoTest() override {
WebUIControllerFactory::UnregisterFactoryForTesting(&factory_);
}
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(), GURL("chrome://dummy-web-ui")));
constexpr char kChromeUIMojoWebUIOrigin[] = "chrome://mojo-web-ui/";
EXPECT_TRUE(NavigateToURL(shell(), GURL(kChromeUIMojoWebUIOrigin + path)));
}
// Run |script| and return a boolean result.
bool RunBoolFunction(const std::string& script) {
bool result = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell()->web_contents(), "domAutomationController.send(" + script + ")",
&result));
return result;
}
private:
TestWebUIControllerFactory factory_;
DISALLOW_COPY_AND_ASSIGN(WebUIMojoTest);
};
bool IsGeneratedResourceAvailable(const std::string& resource_path) {
// Currently there is no way to have a generated file included in the isolate
// files. If the bindings file doesn't exist assume we're on such a bot and
// pass.
// TODO(sky): remove this conditional when isolates support copying from gen.
base::ScopedAllowBlockingForTesting allow_blocking;
const base::FilePath test_file_path(GetFilePathForJSResource(resource_path));
if (base::PathExists(test_file_path))
return true;
LOG(WARNING) << " mojom binding file doesn't exist, assuming on isolate";
return false;
}
// Loads a webui page that contains mojo bindings and verifies a message makes
// it from the browser to the page and back.
IN_PROC_BROWSER_TEST_F(WebUIMojoTest, EndToEndPing) {
if (!IsGeneratedResourceAvailable(
"content/test/data/web_ui_test_mojo_bindings.mojom.js"))
return;
g_got_message = false;
base::RunLoop run_loop;
factory()->set_run_loop(&run_loop);
GURL test_url("chrome://mojo-web-ui/web_ui_mojo.html?ping");
NavigateToURL(shell(), test_url);
// RunLoop is quit when message received from page.
run_loop.Run();
EXPECT_TRUE(g_got_message);
// Check that a second render frame in the same renderer process works
// correctly.
Shell* other_shell = CreateBrowser();
g_got_message = false;
base::RunLoop other_run_loop;
factory()->set_run_loop(&other_run_loop);
NavigateToURL(other_shell, test_url);
// RunLoop is quit when message received from page.
other_run_loop.Run();
EXPECT_TRUE(g_got_message);
EXPECT_EQ(shell()->web_contents()->GetMainFrame()->GetProcess(),
other_shell->web_contents()->GetMainFrame()->GetProcess());
}
// Disabled due to flakiness: crbug.com/860385.
#if defined(OS_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 defined(OS_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()"));
}
} // namespace
} // namespace content