diff options
author | skyostil <skyostil@chromium.org> | 2016-02-26 12:53:33 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-02-26 20:59:19 +0000 |
commit | fe11616891b5f57ea977c24efdf7946ed2cbf5f7 (patch) | |
tree | 2001cd7ae73c6b716ab8782ae86e578ef7e306df /headless | |
parent | 88eedb7f8965e0134502f1f471f6d9f8fc82a940 (diff) | |
download | chromium_src-fe11616891b5f57ea977c24efdf7946ed2cbf5f7.zip chromium_src-fe11616891b5f57ea977c24efdf7946ed2cbf5f7.tar.gz chromium_src-fe11616891b5f57ea977c24efdf7946ed2cbf5f7.tar.bz2 |
headless: Initial headless embedder API implementation
This patch introduces the initial headless embedder API implementation.
It allows the embedder to initialize a headless browsing environment and
to navigate to a URL.
We also add a headless shell application to demonstrate the use of the
embedder API.
Adapted from a patch by Alexander Timin <altimin@chromium.org>
BUG=546953
Review URL: https://codereview.chromium.org/1674263002
Cr-Commit-Position: refs/heads/master@{#377973}
Diffstat (limited to 'headless')
41 files changed, 2365 insertions, 252 deletions
diff --git a/headless/BUILD.gn b/headless/BUILD.gn index f94e93d..f31584f 100644 --- a/headless/BUILD.gn +++ b/headless/BUILD.gn @@ -2,23 +2,133 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//testing/test.gni") +import("//tools/grit/repack.gni") + group("headless") { deps = [ "//headless:headless_lib", ] } +repack("pak") { + sources = [ + "$root_gen_dir/blink/devtools_resources.pak", + "$root_gen_dir/blink/public/resources/blink_image_resources_100_percent.pak", + "$root_gen_dir/blink/public/resources/blink_resources.pak", + "$root_gen_dir/content/app/resources/content_resources_100_percent.pak", + "$root_gen_dir/content/app/strings/content_strings_en-US.pak", + "$root_gen_dir/content/browser/tracing/tracing_resources.pak", + "$root_gen_dir/content/content_resources.pak", + "$root_gen_dir/net/net_resources.pak", + "$root_gen_dir/ui/resources/ui_resources_100_percent.pak", + "$root_gen_dir/ui/resources/webui_resources.pak", + "$root_gen_dir/ui/strings/app_locale_settings_en-US.pak", + "$root_gen_dir/ui/strings/ui_strings_en-US.pak", + ] + + deps = [ + "//content:resources", + "//content/app/resources", + "//content/app/strings", + "//content/browser/devtools:resources", + "//content/browser/tracing:resources", + "//net:net_resources", + "//third_party/WebKit/public:image_resources", + "//third_party/WebKit/public:resources", + "//ui/resources", + "//ui/strings", + ] + + output = "$root_out_dir/headless_lib.pak" +} + static_library("headless_lib") { sources = [ + "lib/browser/headless_browser_context.cc", + "lib/browser/headless_browser_context.h", + "lib/browser/headless_browser_impl.cc", + "lib/browser/headless_browser_impl.h", + "lib/browser/headless_browser_main_parts.cc", + "lib/browser/headless_browser_main_parts.h", + "lib/browser/headless_content_browser_client.cc", + "lib/browser/headless_content_browser_client.h", + "lib/browser/headless_devtools.cc", + "lib/browser/headless_devtools.h", + "lib/browser/headless_screen.cc", + "lib/browser/headless_screen.h", + "lib/browser/headless_url_request_context_getter.cc", + "lib/browser/headless_url_request_context_getter.h", + "lib/browser/headless_web_contents_impl.cc", + "lib/browser/headless_web_contents_impl.h", + "lib/headless_content_client.cc", + "lib/headless_content_client.h", + "lib/headless_content_main_delegate.cc", + "lib/headless_content_main_delegate.h", + "lib/renderer/headless_content_renderer_client.cc", + "lib/renderer/headless_content_renderer_client.h", + "lib/utility/headless_content_utility_client.cc", + "lib/utility/headless_content_utility_client.h", "public/headless_browser.cc", "public/headless_browser.h", "public/headless_export.h", - "public/network.h", - "public/web_contents.h", - "public/web_frame.h", + "public/headless_web_contents.h", ] deps = [ + ":pak", "//base", + "//components/devtools_http_handler", + "//content/public/browser", + "//content/public/common", + "//content/public/renderer", + "//content/public/utility", + "//net", + "//ui/aura", + "//ui/base", + "//ui/compositor", + "//ui/ozone", + "//url", + ] +} + +group("headless_tests") { + testonly = true + + deps = [ + ":headless_browsertests", + ] +} + +test("headless_browsertests") { + sources = [ + "lib/headless_browser_browsertest.cc", + "lib/headless_web_contents_browsertest.cc", + "test/headless_browser_test.cc", + "test/headless_browser_test.h", + "test/headless_test_launcher.cc", + ] + + defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] + + deps = [ + "//base", + "//content/test:browsertest_base", + "//content/test:test_support", + "//headless:headless_lib", + "//testing/gmock", + "//testing/gtest", + ] +} + +executable("headless_shell") { + testonly = true + + sources = [ + "app/headless_shell.cc", + ] + + deps = [ + "//headless:headless_lib", ] } diff --git a/headless/DEPS b/headless/DEPS new file mode 100644 index 0000000..b251cd9 --- /dev/null +++ b/headless/DEPS @@ -0,0 +1,10 @@ +include_rules = [ + "+components/devtools_http_handler", + "+content/public", + "+net", + "+ui/base", + "+ui/base/resource", + "+ui/gfx", + "+ui/gfx/geometry", + "+ui/ozone/public", +] diff --git a/headless/README.md b/headless/README.md new file mode 100644 index 0000000..733bef4 --- /dev/null +++ b/headless/README.md @@ -0,0 +1,59 @@ +# Headless Chromium + +Headless Chromium is a library for running Chromium in a headless/server +environment. Expected use cases include loading web pages, extracting metadata +(e.g., the DOM) and generating bitmaps from page contents -- using all the +modern web platform features provided by Chromium and Blink. + +## Headless shell + +The headless shell is a sample application which demonstrates the use of the +headless API. To run it, first open the build configuration editor: + +``` +$ gn args out/Release +``` + +and enable headless mode with `is_headless = true`. + +Then build the shell: + +``` +$ ninja -C out/Release headless_shell +``` + +After the build completes, the headless shell can be run with the following +command: + +``` +$ out/Release/headless_shell https://www.google.com +``` + +To attach a [DevTools](https://developer.chrome.com/devtools) debugger to the +shell, start it with an argument specifying the debugging port: + +``` +$ out/Release/headless_shell --remote-debugging-port=9222 https://youtube.com +``` + +Then navigate to `http://127.0.0.1:9222` with your browser. + +## Embedder API + +The embedder API allows developers to integrate the headless library into their +application. The API provides default implementations for low level adaptation +points such as networking and the run loop. + +The main embedder API classes are: + +- `HeadlessBrowser::Options::Builder` - Defines the embedding options, e.g.: + - `SetMessagePump` - Replaces the default base message pump. See + `base::MessagePump`. + +## Headless API + +The headless API is used to drive the browser and interact with the loaded web +pages. Its main classes are: + +- `HeadlessBrowser` - Represents the global headless browser instance. +- `HeadlessWebContents` - Represents a single "tab" within the browser. diff --git a/headless/app/headless_shell.cc b/headless/app/headless_shell.cc new file mode 100644 index 0000000..9375746 --- /dev/null +++ b/headless/app/headless_shell.cc @@ -0,0 +1,103 @@ +// Copyright 2016 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 "base/bind.h" +#include "base/callback.h" +#include "base/command_line.h" +#include "base/location.h" +#include "base/memory/ref_counted.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/string_number_conversions.h" +#include "content/public/common/content_switches.h" +#include "headless/public/headless_browser.h" +#include "headless/public/headless_web_contents.h" +#include "net/base/ip_address.h" +#include "ui/gfx/geometry/size.h" + +using headless::HeadlessBrowser; +using headless::HeadlessWebContents; + +namespace { +// Address where to listen to incoming DevTools connections. +const char kDevToolsHttpServerAddress[] = "127.0.0.1"; +} + +// A sample application which demonstrates the use of the headless API. +class HeadlessShell : public HeadlessWebContents::Observer { + public: + HeadlessShell() : browser_(nullptr) {} + ~HeadlessShell() override { + if (web_contents_) + web_contents_->RemoveObserver(this); + } + + void OnStart(HeadlessBrowser* browser) { + browser_ = browser; + web_contents_ = browser->CreateWebContents(gfx::Size(800, 600)); + web_contents_->AddObserver(this); + + base::CommandLine::StringVector args = + base::CommandLine::ForCurrentProcess()->GetArgs(); + + const char kDefaultUrl[] = "about:blank"; + GURL url; + if (args.empty() || args[0].empty()) { + url = GURL(kDefaultUrl); + } else { + url = GURL(args[0]); + } + if (!web_contents_->OpenURL(url)) { + LOG(ERROR) << "Navigation failed"; + web_contents_ = nullptr; + browser_->Shutdown(); + } + } + + void ShutdownIfNeeded() { + const base::CommandLine& command_line = + *base::CommandLine::ForCurrentProcess(); + if (!command_line.HasSwitch(switches::kRemoteDebuggingPort)) { + web_contents_ = nullptr; + browser_->Shutdown(); + } + } + + // HeadlessWebContents::Observer implementation: + void DocumentOnLoadCompletedInMainFrame() override { + LOG(DEBUG) << "Document load completed"; + ShutdownIfNeeded(); + } + + private: + HeadlessBrowser* browser_; // Not owned. + scoped_ptr<HeadlessWebContents> web_contents_; + + DISALLOW_COPY_AND_ASSIGN(HeadlessShell); +}; + +int main(int argc, const char** argv) { + HeadlessShell shell; + HeadlessBrowser::Options::Builder builder(argc, argv); + + // Enable devtools if requested. + base::CommandLine command_line(argc, argv); + if (command_line.HasSwitch(switches::kRemoteDebuggingPort)) { + int parsed_port; + std::string port_str = + command_line.GetSwitchValueASCII(switches::kRemoteDebuggingPort); + if (base::StringToInt(port_str, &parsed_port) && + base::IsValueInRangeForNumericType<uint16_t>(parsed_port)) { + net::IPAddress devtools_address; + bool result = + devtools_address.AssignFromIPLiteral(kDevToolsHttpServerAddress); + DCHECK(result); + builder.EnableDevToolsServer(net::IPEndPoint( + devtools_address, base::checked_cast<uint16_t>(parsed_port))); + } + } + + return HeadlessBrowserMain( + builder.Build(), + base::Bind(&HeadlessShell::OnStart, base::Unretained(&shell))); +} diff --git a/headless/lib/browser/DEPS b/headless/lib/browser/DEPS new file mode 100644 index 0000000..75ab3c7 --- /dev/null +++ b/headless/lib/browser/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+ui/aura" +] diff --git a/headless/lib/browser/headless_browser_context.cc b/headless/lib/browser/headless_browser_context.cc new file mode 100644 index 0000000..aca8351 --- /dev/null +++ b/headless/lib/browser/headless_browser_context.cc @@ -0,0 +1,163 @@ +// Copyright 2015 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 "headless/lib/browser/headless_browser_context.h" + +#include "base/path_service.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/resource_context.h" +#include "content/public/browser/storage_partition.h" +#include "headless/lib/browser/headless_url_request_context_getter.h" +#include "net/url_request/url_request_context.h" + +namespace headless { + +// Contains net::URLRequestContextGetter required for resource loading. +// Must be destructed on the IO thread as per content::ResourceContext +// requirements. +class HeadlessResourceContext : public content::ResourceContext { + public: + HeadlessResourceContext(); + ~HeadlessResourceContext() override; + + // ResourceContext implementation: + net::HostResolver* GetHostResolver() override; + net::URLRequestContext* GetRequestContext() override; + + // Configure the URL request context getter to be used for resource fetching. + // Must be called before any of the other methods of this class are used. Must + // be called on the browser UI thread. + void set_url_request_context_getter( + scoped_refptr<net::URLRequestContextGetter> url_request_context_getter) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + url_request_context_getter_ = std::move(url_request_context_getter); + } + + private: + scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_; + + DISALLOW_COPY_AND_ASSIGN(HeadlessResourceContext); +}; + +HeadlessResourceContext::HeadlessResourceContext() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); +} + +HeadlessResourceContext::~HeadlessResourceContext() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); +} + +net::HostResolver* HeadlessResourceContext::GetHostResolver() { + CHECK(url_request_context_getter_); + return url_request_context_getter_->GetURLRequestContext()->host_resolver(); +} + +net::URLRequestContext* HeadlessResourceContext::GetRequestContext() { + CHECK(url_request_context_getter_); + return url_request_context_getter_->GetURLRequestContext(); +} + +HeadlessBrowserContext::HeadlessBrowserContext( + const HeadlessBrowser::Options& options) + : resource_context_(new HeadlessResourceContext), options_(options) { + InitWhileIOAllowed(); +} + +HeadlessBrowserContext::~HeadlessBrowserContext() { + if (resource_context_) { + content::BrowserThread::DeleteSoon(content::BrowserThread::IO, FROM_HERE, + resource_context_.release()); + } +} + +void HeadlessBrowserContext::InitWhileIOAllowed() { + // TODO(skyostil): Allow the embedder to override this. + PathService::Get(base::DIR_EXE, &path_); +} + +scoped_ptr<content::ZoomLevelDelegate> +HeadlessBrowserContext::CreateZoomLevelDelegate( + const base::FilePath& partition_path) { + return scoped_ptr<content::ZoomLevelDelegate>(); +} + +base::FilePath HeadlessBrowserContext::GetPath() const { + return path_; +} + +bool HeadlessBrowserContext::IsOffTheRecord() const { + return false; +} + +net::URLRequestContextGetter* HeadlessBrowserContext::GetRequestContext() { + return GetDefaultStoragePartition(this)->GetURLRequestContext(); +} + +net::URLRequestContextGetter* +HeadlessBrowserContext::GetRequestContextForRenderProcess( + int renderer_child_id) { + return GetRequestContext(); +} + +net::URLRequestContextGetter* HeadlessBrowserContext::GetMediaRequestContext() { + return GetRequestContext(); +} + +net::URLRequestContextGetter* +HeadlessBrowserContext::GetMediaRequestContextForRenderProcess( + int renderer_child_id) { + return GetRequestContext(); +} + +net::URLRequestContextGetter* +HeadlessBrowserContext::GetMediaRequestContextForStoragePartition( + const base::FilePath& partition_path, + bool in_memory) { + return GetRequestContext(); +} + +content::ResourceContext* HeadlessBrowserContext::GetResourceContext() { + return resource_context_.get(); +} + +content::DownloadManagerDelegate* +HeadlessBrowserContext::GetDownloadManagerDelegate() { + return nullptr; +} + +content::BrowserPluginGuestManager* HeadlessBrowserContext::GetGuestManager() { + // TODO(altimin): Should be non-null? (is null in content/shell). + return nullptr; +} + +storage::SpecialStoragePolicy* +HeadlessBrowserContext::GetSpecialStoragePolicy() { + return nullptr; +} + +content::PushMessagingService* +HeadlessBrowserContext::GetPushMessagingService() { + return nullptr; +} + +content::SSLHostStateDelegate* +HeadlessBrowserContext::GetSSLHostStateDelegate() { + return nullptr; +} + +content::PermissionManager* HeadlessBrowserContext::GetPermissionManager() { + return nullptr; +} + +content::BackgroundSyncController* +HeadlessBrowserContext::GetBackgroundSyncController() { + return nullptr; +} + +void HeadlessBrowserContext::SetURLRequestContextGetter( + scoped_refptr<net::URLRequestContextGetter> url_request_context_getter) { + resource_context_->set_url_request_context_getter(url_request_context_getter); +} + +} // namespace headless diff --git a/headless/lib/browser/headless_browser_context.h b/headless/lib/browser/headless_browser_context.h new file mode 100644 index 0000000..41b7211 --- /dev/null +++ b/headless/lib/browser/headless_browser_context.h @@ -0,0 +1,67 @@ +// Copyright 2015 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 HEADLESS_LIB_BROWSER_HEADLESS_BROWSER_CONTEXT_H_ +#define HEADLESS_LIB_BROWSER_HEADLESS_BROWSER_CONTEXT_H_ + +#include "base/files/file_path.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/content_browser_client.h" +#include "content/public/browser/resource_context.h" +#include "headless/lib/browser/headless_url_request_context_getter.h" +#include "headless/public/headless_browser.h" + +namespace headless { +class HeadlessResourceContext; + +class HeadlessBrowserContext : public content::BrowserContext { + public: + explicit HeadlessBrowserContext(const HeadlessBrowser::Options& options); + ~HeadlessBrowserContext() override; + + // BrowserContext implementation: + scoped_ptr<content::ZoomLevelDelegate> CreateZoomLevelDelegate( + const base::FilePath& partition_path) override; + base::FilePath GetPath() const override; + bool IsOffTheRecord() const override; + net::URLRequestContextGetter* GetRequestContext() override; + net::URLRequestContextGetter* GetRequestContextForRenderProcess( + int renderer_child_id) override; + net::URLRequestContextGetter* GetMediaRequestContext() override; + net::URLRequestContextGetter* GetMediaRequestContextForRenderProcess( + int renderer_child_id) override; + net::URLRequestContextGetter* GetMediaRequestContextForStoragePartition( + const base::FilePath& partition_path, + bool in_memory) override; + content::ResourceContext* GetResourceContext() override; + content::DownloadManagerDelegate* GetDownloadManagerDelegate() override; + content::BrowserPluginGuestManager* GetGuestManager() override; + storage::SpecialStoragePolicy* GetSpecialStoragePolicy() override; + content::PushMessagingService* GetPushMessagingService() override; + content::SSLHostStateDelegate* GetSSLHostStateDelegate() override; + content::PermissionManager* GetPermissionManager() override; + content::BackgroundSyncController* GetBackgroundSyncController() override; + + const HeadlessBrowser::Options& options() const { return options_; } + + // Configure the URL request context getter to be used for serving URL + // requests in this browser instance. + void SetURLRequestContextGetter( + scoped_refptr<net::URLRequestContextGetter> url_request_context_getter); + + private: + // Performs initialization of the HeadlessBrowserContext while IO is still + // allowed on the current thread. + void InitWhileIOAllowed(); + + base::FilePath path_; + scoped_ptr<HeadlessResourceContext> resource_context_; + HeadlessBrowser::Options options_; + + DISALLOW_COPY_AND_ASSIGN(HeadlessBrowserContext); +}; + +} // namespace headless + +#endif // HEADLESS_LIB_BROWSER_HEADLESS_BROWSER_CONTEXT_H_ diff --git a/headless/lib/browser/headless_browser_impl.cc b/headless/lib/browser/headless_browser_impl.cc new file mode 100644 index 0000000..6bc2b5d --- /dev/null +++ b/headless/lib/browser/headless_browser_impl.cc @@ -0,0 +1,93 @@ +// Copyright 2015 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 "headless/lib/browser/headless_browser_impl.h" + +#include "base/thread_task_runner_handle.h" +#include "content/public/app/content_main.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/web_contents.h" +#include "headless/lib/browser/headless_browser_context.h" +#include "headless/lib/browser/headless_browser_main_parts.h" +#include "headless/lib/browser/headless_web_contents_impl.h" +#include "headless/lib/headless_content_main_delegate.h" +#include "ui/aura/env.h" +#include "ui/aura/window_tree_host.h" +#include "ui/gfx/geometry/size.h" + +namespace headless { + +HeadlessBrowserImpl::HeadlessBrowserImpl( + const base::Callback<void(HeadlessBrowser*)>& on_start_callback, + const HeadlessBrowser::Options& options) + : on_start_callback_(on_start_callback), + options_(options), + browser_main_parts_(nullptr) { + DCHECK(!on_start_callback_.is_null()); +} + +HeadlessBrowserImpl::~HeadlessBrowserImpl() {} + +scoped_ptr<HeadlessWebContents> HeadlessBrowserImpl::CreateWebContents( + const gfx::Size& size) { + DCHECK(BrowserMainThread()->BelongsToCurrentThread()); + return make_scoped_ptr(new HeadlessWebContentsImpl( + browser_context(), window_tree_host_->window(), size)); +} + +scoped_refptr<base::SingleThreadTaskRunner> +HeadlessBrowserImpl::BrowserMainThread() const { + return content::BrowserThread::GetMessageLoopProxyForThread( + content::BrowserThread::UI); +} + +void HeadlessBrowserImpl::Shutdown() { + DCHECK(BrowserMainThread()->BelongsToCurrentThread()); + BrowserMainThread()->PostTask(FROM_HERE, + base::MessageLoop::QuitWhenIdleClosure()); +} + +HeadlessBrowserContext* HeadlessBrowserImpl::browser_context() const { + DCHECK(BrowserMainThread()->BelongsToCurrentThread()); + DCHECK(browser_main_parts()); + return browser_main_parts()->browser_context(); +} + +HeadlessBrowserMainParts* HeadlessBrowserImpl::browser_main_parts() const { + DCHECK(BrowserMainThread()->BelongsToCurrentThread()); + return browser_main_parts_; +} + +void HeadlessBrowserImpl::set_browser_main_parts( + HeadlessBrowserMainParts* browser_main_parts) { + DCHECK(!browser_main_parts_); + browser_main_parts_ = browser_main_parts; +} + +void HeadlessBrowserImpl::RunOnStartCallback() { + DCHECK(aura::Env::GetInstance()); + window_tree_host_.reset(aura::WindowTreeHost::Create(gfx::Rect())); + window_tree_host_->InitHost(); + + on_start_callback_.Run(this); + on_start_callback_ = base::Callback<void(HeadlessBrowser*)>(); +} + +int HeadlessBrowserMain( + const HeadlessBrowser::Options& options, + const base::Callback<void(HeadlessBrowser*)>& on_browser_start_callback) { + scoped_ptr<HeadlessBrowserImpl> browser( + new HeadlessBrowserImpl(on_browser_start_callback, options)); + + // TODO(skyostil): Implement custom message pumps. + DCHECK(!options.message_pump); + + headless::HeadlessContentMainDelegate delegate(std::move(browser)); + content::ContentMainParams params(&delegate); + params.argc = options.argc; + params.argv = options.argv; + return content::ContentMain(params); +} + +} // namespace headless diff --git a/headless/lib/browser/headless_browser_impl.h b/headless/lib/browser/headless_browser_impl.h new file mode 100644 index 0000000..2f276c8 --- /dev/null +++ b/headless/lib/browser/headless_browser_impl.h @@ -0,0 +1,58 @@ +// Copyright 2015 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 HEADLESS_LIB_BROWSER_HEADLESS_BROWSER_IMPL_H_ +#define HEADLESS_LIB_BROWSER_HEADLESS_BROWSER_IMPL_H_ + +#include "headless/public/headless_browser.h" + +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" +#include "headless/lib/browser/headless_web_contents_impl.h" + +namespace aura { +class WindowTreeHost; +} + +namespace headless { + +class HeadlessBrowserContext; +class HeadlessBrowserMainParts; + +class HeadlessBrowserImpl : public HeadlessBrowser { + public: + HeadlessBrowserImpl( + const base::Callback<void(HeadlessBrowser*)>& on_start_callback, + const HeadlessBrowser::Options& options); + ~HeadlessBrowserImpl() override; + + // HeadlessBrowser implementation: + scoped_ptr<HeadlessWebContents> CreateWebContents( + const gfx::Size& size) override; + scoped_refptr<base::SingleThreadTaskRunner> BrowserMainThread() + const override; + + void Shutdown() override; + + void set_browser_main_parts(HeadlessBrowserMainParts* browser_main_parts); + HeadlessBrowserMainParts* browser_main_parts() const; + + HeadlessBrowserContext* browser_context() const; + + void RunOnStartCallback(); + + const HeadlessBrowser::Options& options() const { return options_; } + + protected: + base::Callback<void(HeadlessBrowser*)> on_start_callback_; + HeadlessBrowser::Options options_; + HeadlessBrowserMainParts* browser_main_parts_; // Not owned. + scoped_ptr<aura::WindowTreeHost> window_tree_host_; + + DISALLOW_COPY_AND_ASSIGN(HeadlessBrowserImpl); +}; + +} // namespace headless + +#endif // HEADLESS_LIB_BROWSER_HEADLESS_BROWSER_IMPL_H_ diff --git a/headless/lib/browser/headless_browser_main_parts.cc b/headless/lib/browser/headless_browser_main_parts.cc new file mode 100644 index 0000000..c85ba35 --- /dev/null +++ b/headless/lib/browser/headless_browser_main_parts.cc @@ -0,0 +1,54 @@ +// Copyright 2015 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 "headless/lib/browser/headless_browser_main_parts.h" + +#include "components/devtools_http_handler/devtools_http_handler.h" +#include "headless/lib/browser/headless_browser_context.h" +#include "headless/lib/browser/headless_browser_impl.h" +#include "headless/lib/browser/headless_devtools.h" +#include "headless/lib/browser/headless_screen.h" +#include "ui/aura/env.h" +#include "ui/gfx/screen.h" + +namespace headless { + +namespace { + +void PlatformInitialize() { + HeadlessScreen* screen = HeadlessScreen::Create(gfx::Size()); + gfx::Screen::SetScreenInstance(screen); +} + +void PlatformExit() { + aura::Env::DeleteInstance(); +} + +} // namespace + +HeadlessBrowserMainParts::HeadlessBrowserMainParts(HeadlessBrowserImpl* browser) + : browser_(browser) {} + +HeadlessBrowserMainParts::~HeadlessBrowserMainParts() {} + +void HeadlessBrowserMainParts::PreMainMessageLoopRun() { + browser_context_.reset(new HeadlessBrowserContext(browser_->options())); + if (browser_->options().devtools_endpoint.address().IsValid()) { + devtools_http_handler_ = + CreateLocalDevToolsHttpHandler(browser_context_.get()); + } + PlatformInitialize(); +} + +void HeadlessBrowserMainParts::PostMainMessageLoopRun() { + browser_context_.reset(); + devtools_http_handler_.reset(); + PlatformExit(); +} + +HeadlessBrowserContext* HeadlessBrowserMainParts::browser_context() const { + return browser_context_.get(); +} + +} // namespace headless diff --git a/headless/lib/browser/headless_browser_main_parts.h b/headless/lib/browser/headless_browser_main_parts.h new file mode 100644 index 0000000..f666065 --- /dev/null +++ b/headless/lib/browser/headless_browser_main_parts.h @@ -0,0 +1,42 @@ +// Copyright 2015 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 HEADLESS_LIB_BROWSER_HEADLESS_BROWSER_MAIN_PARTS_H_ +#define HEADLESS_LIB_BROWSER_HEADLESS_BROWSER_MAIN_PARTS_H_ + +#include "base/memory/scoped_ptr.h" +#include "content/public/browser/browser_main_parts.h" +#include "headless/public/headless_browser.h" + +namespace devtools_http_handler { +class DevToolsHttpHandler; +} + +namespace headless { + +class HeadlessBrowserContext; +class HeadlessBrowserImpl; + +class HeadlessBrowserMainParts : public content::BrowserMainParts { + public: + explicit HeadlessBrowserMainParts(HeadlessBrowserImpl* browser); + ~HeadlessBrowserMainParts() override; + + // content::BrowserMainParts implementation: + void PreMainMessageLoopRun() override; + void PostMainMessageLoopRun() override; + + HeadlessBrowserContext* browser_context() const; + + private: + HeadlessBrowserImpl* browser_; // Not owned. + scoped_ptr<HeadlessBrowserContext> browser_context_; + scoped_ptr<devtools_http_handler::DevToolsHttpHandler> devtools_http_handler_; + + DISALLOW_COPY_AND_ASSIGN(HeadlessBrowserMainParts); +}; + +} // namespace headless + +#endif // HEADLESS_LIB_BROWSER_HEADLESS_BROWSER_MAIN_PARTS_H_ diff --git a/headless/lib/browser/headless_content_browser_client.cc b/headless/lib/browser/headless_content_browser_client.cc new file mode 100644 index 0000000..fb718af --- /dev/null +++ b/headless/lib/browser/headless_content_browser_client.cc @@ -0,0 +1,51 @@ +// Copyright 2015 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 "headless/lib/browser/headless_content_browser_client.h" + +#include "content/public/browser/browser_thread.h" +#include "headless/lib/browser/headless_browser_context.h" +#include "headless/lib/browser/headless_browser_impl.h" +#include "headless/lib/browser/headless_browser_main_parts.h" + +namespace headless { + +HeadlessContentBrowserClient::HeadlessContentBrowserClient( + HeadlessBrowserImpl* browser) + : browser_(browser) {} + +HeadlessContentBrowserClient::~HeadlessContentBrowserClient() {} + +content::BrowserMainParts* HeadlessContentBrowserClient::CreateBrowserMainParts( + const content::MainFunctionParams&) { + scoped_ptr<HeadlessBrowserMainParts> browser_main_parts = + make_scoped_ptr(new HeadlessBrowserMainParts(browser_)); + browser_->set_browser_main_parts(browser_main_parts.get()); + return browser_main_parts.release(); +} + +net::URLRequestContextGetter* +HeadlessContentBrowserClient::CreateRequestContext( + content::BrowserContext* content_browser_context, + content::ProtocolHandlerMap* protocol_handlers, + content::URLRequestInterceptorScopedVector request_interceptors) { + CHECK(content_browser_context == browser_context()); + scoped_refptr<HeadlessURLRequestContextGetter> url_request_context_getter( + new HeadlessURLRequestContextGetter( + false /* ignore_certificate_errors */, browser_context()->GetPath(), + content::BrowserThread::GetMessageLoopProxyForThread( + content::BrowserThread::IO), + content::BrowserThread::GetMessageLoopProxyForThread( + content::BrowserThread::FILE), + protocol_handlers, std::move(request_interceptors), + nullptr /* net_log */, browser_context()->options())); + browser_context()->SetURLRequestContextGetter(url_request_context_getter); + return url_request_context_getter.get(); +} + +HeadlessBrowserContext* HeadlessContentBrowserClient::browser_context() const { + return browser_->browser_context(); +} + +} // namespace headless diff --git a/headless/lib/browser/headless_content_browser_client.h b/headless/lib/browser/headless_content_browser_client.h new file mode 100644 index 0000000..cf124e1 --- /dev/null +++ b/headless/lib/browser/headless_content_browser_client.h @@ -0,0 +1,39 @@ +// Copyright 2015 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 HEADLESS_LIB_BROWSER_HEADLESS_CONTENT_BROWSER_CLIENT_H_ +#define HEADLESS_LIB_BROWSER_HEADLESS_CONTENT_BROWSER_CLIENT_H_ + +#include "content/public/browser/content_browser_client.h" + +namespace headless { + +class HeadlessBrowserImpl; +class HeadlessBrowserMainParts; +class HeadlessBrowserContext; + +class HeadlessContentBrowserClient : public content::ContentBrowserClient { + public: + explicit HeadlessContentBrowserClient(HeadlessBrowserImpl* browser); + ~HeadlessContentBrowserClient() override; + + // content::ContentBrowserClient implementation: + content::BrowserMainParts* CreateBrowserMainParts( + const content::MainFunctionParams&) override; + net::URLRequestContextGetter* CreateRequestContext( + content::BrowserContext* browser_context, + content::ProtocolHandlerMap* protocol_handlers, + content::URLRequestInterceptorScopedVector request_interceptors) override; + + HeadlessBrowserContext* browser_context() const; + + private: + HeadlessBrowserImpl* browser_; // Not owned. + + DISALLOW_COPY_AND_ASSIGN(HeadlessContentBrowserClient); +}; + +} // namespace headless + +#endif // HEADLESS_LIB_BROWSER_HEADLESS_CONTENT_BROWSER_CLIENT_H_ diff --git a/headless/lib/browser/headless_devtools.cc b/headless/lib/browser/headless_devtools.cc new file mode 100644 index 0000000..8fe4b7d --- /dev/null +++ b/headless/lib/browser/headless_devtools.cc @@ -0,0 +1,102 @@ +// Copyright 2015 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 "headless/lib/browser/headless_devtools.h" + +#include "base/files/file_path.h" +#include "components/devtools_http_handler/devtools_http_handler.h" +#include "components/devtools_http_handler/devtools_http_handler_delegate.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/devtools_frontend_host.h" +#include "content/public/browser/navigation_entry.h" +#include "headless/lib/browser/headless_browser_context.h" +#include "net/base/net_errors.h" +#include "net/socket/tcp_server_socket.h" +#include "ui/base/resource/resource_bundle.h" + +using devtools_http_handler::DevToolsHttpHandler; + +namespace headless { + +namespace { + +const int kBackLog = 10; + +class TCPServerSocketFactory : public DevToolsHttpHandler::ServerSocketFactory { + public: + TCPServerSocketFactory(const net::IPEndPoint& endpoint) + : endpoint_(endpoint) { + DCHECK(endpoint_.address().IsValid()); + } + + private: + // DevToolsHttpHandler::ServerSocketFactory implementation: + scoped_ptr<net::ServerSocket> CreateForHttpServer() override { + scoped_ptr<net::ServerSocket> socket( + new net::TCPServerSocket(nullptr, net::NetLog::Source())); + if (socket->Listen(endpoint_, kBackLog) != net::OK) + return scoped_ptr<net::ServerSocket>(); + + return socket; + } + + net::IPEndPoint endpoint_; + + DISALLOW_COPY_AND_ASSIGN(TCPServerSocketFactory); +}; + +class HeadlessDevToolsDelegate + : public devtools_http_handler::DevToolsHttpHandlerDelegate { + public: + HeadlessDevToolsDelegate(); + ~HeadlessDevToolsDelegate() override; + + // devtools_http_handler::DevToolsHttpHandlerDelegate implementation: + std::string GetDiscoveryPageHTML() override; + std::string GetFrontendResource(const std::string& path) override; + std::string GetPageThumbnailData(const GURL& url) override; + content::DevToolsExternalAgentProxyDelegate* HandleWebSocketConnection( + const std::string& path) override; + + private: + DISALLOW_COPY_AND_ASSIGN(HeadlessDevToolsDelegate); +}; + +HeadlessDevToolsDelegate::HeadlessDevToolsDelegate() {} + +HeadlessDevToolsDelegate::~HeadlessDevToolsDelegate() {} + +std::string HeadlessDevToolsDelegate::GetDiscoveryPageHTML() { + return std::string(); +} + +std::string HeadlessDevToolsDelegate::GetFrontendResource( + const std::string& path) { + return content::DevToolsFrontendHost::GetFrontendResource(path).as_string(); +} + +std::string HeadlessDevToolsDelegate::GetPageThumbnailData(const GURL& url) { + return std::string(); +} + +content::DevToolsExternalAgentProxyDelegate* +HeadlessDevToolsDelegate::HandleWebSocketConnection(const std::string& path) { + return nullptr; +} + +} // namespace + +scoped_ptr<DevToolsHttpHandler> CreateLocalDevToolsHttpHandler( + HeadlessBrowserContext* browser_context) { + const net::IPEndPoint& endpoint = + browser_context->options().devtools_endpoint; + scoped_ptr<DevToolsHttpHandler::ServerSocketFactory> socket_factory( + new TCPServerSocketFactory(endpoint)); + return make_scoped_ptr(new DevToolsHttpHandler( + std::move(socket_factory), std::string(), new HeadlessDevToolsDelegate(), + browser_context->GetPath(), base::FilePath(), std::string(), + browser_context->options().user_agent)); +} + +} // namespace headless diff --git a/headless/lib/browser/headless_devtools.h b/headless/lib/browser/headless_devtools.h new file mode 100644 index 0000000..96f6333 --- /dev/null +++ b/headless/lib/browser/headless_devtools.h @@ -0,0 +1,24 @@ +// Copyright 2015 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 HEADLESS_LIB_BROWSER_HEADLESS_DEVTOOLS_H_ +#define HEADLESS_LIB_BROWSER_HEADLESS_DEVTOOLS_H_ + +#include "base/memory/scoped_ptr.h" + +namespace devtools_http_handler { +class DevToolsHttpHandler; +} + +namespace headless { +class HeadlessBrowserContext; + +// Starts a DevTools HTTP handler on the loopback interface on the port +// configured by HeadlessBrowser::Options. +scoped_ptr<devtools_http_handler::DevToolsHttpHandler> +CreateLocalDevToolsHttpHandler(HeadlessBrowserContext* browser_context); + +} // namespace content + +#endif // HEADLESS_LIB_BROWSER_HEADLESS_DEVTOOLS_H_ diff --git a/headless/lib/browser/headless_screen.cc b/headless/lib/browser/headless_screen.cc new file mode 100644 index 0000000..6d6f607 --- /dev/null +++ b/headless/lib/browser/headless_screen.cc @@ -0,0 +1,174 @@ +// Copyright 2016 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 "headless/lib/browser/headless_screen.h" + +#include <stdint.h> + +#include "base/logging.h" +#include "ui/aura/env.h" +#include "ui/aura/window.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/aura/window_tree_host.h" +#include "ui/base/ime/input_method.h" +#include "ui/gfx/geometry/rect_conversions.h" +#include "ui/gfx/geometry/size_conversions.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/screen.h" + +namespace headless { + +namespace { + +bool IsRotationPortrait(gfx::Display::Rotation rotation) { + return rotation == gfx::Display::ROTATE_90 || + rotation == gfx::Display::ROTATE_270; +} + +} // namespace + +// static +HeadlessScreen* HeadlessScreen::Create(const gfx::Size& size) { + const gfx::Size kDefaultSize(800, 600); + return new HeadlessScreen(gfx::Rect(size.IsEmpty() ? kDefaultSize : size)); +} + +HeadlessScreen::~HeadlessScreen() {} + +aura::WindowTreeHost* HeadlessScreen::CreateHostForPrimaryDisplay() { + DCHECK(!host_); + host_ = aura::WindowTreeHost::Create(gfx::Rect(display_.GetSizeInPixel())); + // Some tests don't correctly manage window focus/activation states. + // Makes sure InputMethod is default focused so that IME basics can work. + host_->GetInputMethod()->OnFocus(); + host_->window()->AddObserver(this); + host_->InitHost(); + return host_; +} + +void HeadlessScreen::SetDeviceScaleFactor(float device_scale_factor) { + gfx::Rect bounds_in_pixel(display_.GetSizeInPixel()); + display_.SetScaleAndBounds(device_scale_factor, bounds_in_pixel); +} + +void HeadlessScreen::SetDisplayRotation(gfx::Display::Rotation rotation) { + gfx::Rect bounds_in_pixel(display_.GetSizeInPixel()); + gfx::Rect new_bounds(bounds_in_pixel); + if (IsRotationPortrait(rotation) != IsRotationPortrait(display_.rotation())) { + new_bounds.set_width(bounds_in_pixel.height()); + new_bounds.set_height(bounds_in_pixel.width()); + } + display_.set_rotation(rotation); + display_.SetScaleAndBounds(display_.device_scale_factor(), new_bounds); + host_->SetRootTransform(GetRotationTransform() * GetUIScaleTransform()); +} + +void HeadlessScreen::SetUIScale(float ui_scale) { + ui_scale_ = ui_scale; + gfx::Rect bounds_in_pixel(display_.GetSizeInPixel()); + gfx::Rect new_bounds = gfx::ToNearestRect( + gfx::ScaleRect(gfx::RectF(bounds_in_pixel), 1.0f / ui_scale)); + display_.SetScaleAndBounds(display_.device_scale_factor(), new_bounds); + host_->SetRootTransform(GetRotationTransform() * GetUIScaleTransform()); +} + +void HeadlessScreen::SetWorkAreaInsets(const gfx::Insets& insets) { + display_.UpdateWorkAreaFromInsets(insets); +} + +gfx::Transform HeadlessScreen::GetRotationTransform() const { + gfx::Transform rotate; + switch (display_.rotation()) { + case gfx::Display::ROTATE_0: + break; + case gfx::Display::ROTATE_90: + rotate.Translate(display_.bounds().height(), 0); + rotate.Rotate(90); + break; + case gfx::Display::ROTATE_270: + rotate.Translate(0, display_.bounds().width()); + rotate.Rotate(270); + break; + case gfx::Display::ROTATE_180: + rotate.Translate(display_.bounds().width(), display_.bounds().height()); + rotate.Rotate(180); + break; + } + + return rotate; +} + +gfx::Transform HeadlessScreen::GetUIScaleTransform() const { + gfx::Transform ui_scale; + ui_scale.Scale(1.0f / ui_scale_, 1.0f / ui_scale_); + return ui_scale; +} + +void HeadlessScreen::OnWindowBoundsChanged(aura::Window* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + DCHECK_EQ(host_->window(), window); + display_.SetSize(gfx::ScaleToFlooredSize(new_bounds.size(), + display_.device_scale_factor())); +} + +void HeadlessScreen::OnWindowDestroying(aura::Window* window) { + if (host_->window() == window) + host_ = NULL; +} + +gfx::Point HeadlessScreen::GetCursorScreenPoint() { + return aura::Env::GetInstance()->last_mouse_location(); +} + +gfx::NativeWindow HeadlessScreen::GetWindowUnderCursor() { + return GetWindowAtScreenPoint(GetCursorScreenPoint()); +} + +gfx::NativeWindow HeadlessScreen::GetWindowAtScreenPoint( + const gfx::Point& point) { + if (!host_ || !host_->window()) + return nullptr; + return host_->window()->GetTopWindowContainingPoint(point); +} + +int HeadlessScreen::GetNumDisplays() const { + return 1; +} + +std::vector<gfx::Display> HeadlessScreen::GetAllDisplays() const { + return std::vector<gfx::Display>(1, display_); +} + +gfx::Display HeadlessScreen::GetDisplayNearestWindow( + gfx::NativeWindow window) const { + return display_; +} + +gfx::Display HeadlessScreen::GetDisplayNearestPoint( + const gfx::Point& point) const { + return display_; +} + +gfx::Display HeadlessScreen::GetDisplayMatching( + const gfx::Rect& match_rect) const { + return display_; +} + +gfx::Display HeadlessScreen::GetPrimaryDisplay() const { + return display_; +} + +void HeadlessScreen::AddObserver(gfx::DisplayObserver* observer) {} + +void HeadlessScreen::RemoveObserver(gfx::DisplayObserver* observer) {} + +HeadlessScreen::HeadlessScreen(const gfx::Rect& screen_bounds) + : host_(NULL), ui_scale_(1.0f) { + static int64_t synthesized_display_id = 2000; + display_.set_id(synthesized_display_id++); + display_.SetScaleAndBounds(1.0f, screen_bounds); +} + +} // namespace headless diff --git a/headless/lib/browser/headless_screen.h b/headless/lib/browser/headless_screen.h new file mode 100644 index 0000000..c6b6b3b --- /dev/null +++ b/headless/lib/browser/headless_screen.h @@ -0,0 +1,76 @@ +// Copyright 2016 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 HEADLESS_LIB_BROWSER_HEADLESS_SCREEN_H_ +#define HEADLESS_LIB_BROWSER_HEADLESS_SCREEN_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "ui/aura/window_observer.h" +#include "ui/gfx/display.h" +#include "ui/gfx/screen.h" + +namespace gfx { +class Insets; +class Rect; +class Transform; +} + +namespace aura { +class Window; +class WindowTreeHost; +} + +namespace headless { + +class HeadlessScreen : public gfx::Screen, public aura::WindowObserver { + public: + // Creates a gfx::Screen of the specified size. If no size is specified, then + // creates a 800x600 screen. |size| is in physical pixels. + static HeadlessScreen* Create(const gfx::Size& size); + ~HeadlessScreen() override; + + aura::WindowTreeHost* CreateHostForPrimaryDisplay(); + + void SetDeviceScaleFactor(float device_scale_fator); + void SetDisplayRotation(gfx::Display::Rotation rotation); + void SetUIScale(float ui_scale); + void SetWorkAreaInsets(const gfx::Insets& insets); + + protected: + gfx::Transform GetRotationTransform() const; + gfx::Transform GetUIScaleTransform() const; + + // WindowObserver overrides: + void OnWindowBoundsChanged(aura::Window* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) override; + void OnWindowDestroying(aura::Window* window) override; + + // gfx::Screen overrides: + gfx::Point GetCursorScreenPoint() override; + gfx::NativeWindow GetWindowUnderCursor() override; + gfx::NativeWindow GetWindowAtScreenPoint(const gfx::Point& point) override; + int GetNumDisplays() const override; + std::vector<gfx::Display> GetAllDisplays() const override; + gfx::Display GetDisplayNearestWindow(gfx::NativeView view) const override; + gfx::Display GetDisplayNearestPoint(const gfx::Point& point) const override; + gfx::Display GetDisplayMatching(const gfx::Rect& match_rect) const override; + gfx::Display GetPrimaryDisplay() const override; + void AddObserver(gfx::DisplayObserver* observer) override; + void RemoveObserver(gfx::DisplayObserver* observer) override; + + private: + explicit HeadlessScreen(const gfx::Rect& screen_bounds); + + aura::WindowTreeHost* host_; + gfx::Display display_; + float ui_scale_; + + DISALLOW_COPY_AND_ASSIGN(HeadlessScreen); +}; + +} // namespace headless + +#endif // HEADLESS_LIB_BROWSER_HEADLESS_SCREEN_H_ diff --git a/headless/lib/browser/headless_url_request_context_getter.cc b/headless/lib/browser/headless_url_request_context_getter.cc new file mode 100644 index 0000000..4e8a195 --- /dev/null +++ b/headless/lib/browser/headless_url_request_context_getter.cc @@ -0,0 +1,221 @@ +// Copyright 2016 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 "headless/lib/browser/headless_url_request_context_getter.h" + +#include "base/command_line.h" +#include "base/memory/scoped_ptr.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/worker_pool.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/cookie_store_factory.h" +#include "content/public/common/content_switches.h" +#include "net/cert/cert_verifier.h" +#include "net/dns/host_resolver.h" +#include "net/dns/mapped_host_resolver.h" +#include "net/http/http_auth_handler_factory.h" +#include "net/http/http_cache.h" +#include "net/http/http_network_session.h" +#include "net/http/http_server_properties_impl.h" +#include "net/http/transport_security_state.h" +#include "net/proxy/proxy_service.h" +#include "net/ssl/channel_id_service.h" +#include "net/ssl/default_channel_id_store.h" +#include "net/ssl/ssl_config_service_defaults.h" +#include "net/url_request/data_protocol_handler.h" +#include "net/url_request/file_protocol_handler.h" +#include "net/url_request/static_http_user_agent_settings.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_storage.h" +#include "net/url_request/url_request_intercepting_job_factory.h" +#include "net/url_request/url_request_job_factory_impl.h" + +namespace headless { + +namespace { + +void InstallProtocolHandlers(net::URLRequestJobFactoryImpl* job_factory, + content::ProtocolHandlerMap* protocol_handlers) { + for (content::ProtocolHandlerMap::iterator it = protocol_handlers->begin(); + it != protocol_handlers->end(); ++it) { + bool set_protocol = job_factory->SetProtocolHandler( + it->first, make_scoped_ptr(it->second.release())); + DCHECK(set_protocol); + } + protocol_handlers->clear(); +} + +} // namespace + +HeadlessURLRequestContextGetter::HeadlessURLRequestContextGetter( + bool ignore_certificate_errors, + const base::FilePath& base_path, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> file_task_runner, + content::ProtocolHandlerMap* protocol_handlers, + content::URLRequestInterceptorScopedVector request_interceptors, + net::NetLog* net_log, + const HeadlessBrowser::Options& options) + : ignore_certificate_errors_(ignore_certificate_errors), + base_path_(base_path), + io_task_runner_(std::move(io_task_runner)), + file_task_runner_(std::move(file_task_runner)), + net_log_(net_log), + options_(options), + request_interceptors_(std::move(request_interceptors)) { + // Must first be created on the UI thread. + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + std::swap(protocol_handlers_, *protocol_handlers); + + // We must create the proxy config service on the UI loop on Linux because it + // must synchronously run on the glib message loop. This will be passed to + // the URLRequestContextStorage on the IO thread in GetURLRequestContext(). + proxy_config_service_ = GetProxyConfigService(); +} + +HeadlessURLRequestContextGetter::~HeadlessURLRequestContextGetter() {} + +scoped_ptr<net::NetworkDelegate> +HeadlessURLRequestContextGetter::CreateNetworkDelegate() { + return nullptr; +} + +scoped_ptr<net::ProxyConfigService> +HeadlessURLRequestContextGetter::GetProxyConfigService() { + return net::ProxyService::CreateSystemProxyConfigService(io_task_runner_, + file_task_runner_); +} + +scoped_ptr<net::ProxyService> +HeadlessURLRequestContextGetter::GetProxyService() { + return net::ProxyService::CreateUsingSystemProxyResolver( + std::move(proxy_config_service_), 0, url_request_context_->net_log()); +} + +net::URLRequestContext* +HeadlessURLRequestContextGetter::GetURLRequestContext() { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + if (!url_request_context_) { + const base::CommandLine& command_line = + *base::CommandLine::ForCurrentProcess(); + + url_request_context_.reset(new net::URLRequestContext()); + url_request_context_->set_net_log(net_log_); + network_delegate_ = CreateNetworkDelegate(); + url_request_context_->set_network_delegate(network_delegate_.get()); + storage_.reset( + new net::URLRequestContextStorage(url_request_context_.get())); + storage_->set_cookie_store( + content::CreateCookieStore(content::CookieStoreConfig())); + storage_->set_channel_id_service(make_scoped_ptr( + new net::ChannelIDService(new net::DefaultChannelIDStore(nullptr), + base::WorkerPool::GetTaskRunner(true)))); + // TODO(skyostil): Make language settings configurable. + storage_->set_http_user_agent_settings(make_scoped_ptr( + new net::StaticHttpUserAgentSettings("en-us,en", options_.user_agent))); + + scoped_ptr<net::HostResolver> host_resolver( + net::HostResolver::CreateDefaultResolver( + url_request_context_->net_log())); + + storage_->set_cert_verifier(net::CertVerifier::CreateDefault()); + storage_->set_transport_security_state( + make_scoped_ptr(new net::TransportSecurityState)); + storage_->set_proxy_service(GetProxyService()); + storage_->set_ssl_config_service(new net::SSLConfigServiceDefaults); + storage_->set_http_auth_handler_factory( + net::HttpAuthHandlerFactory::CreateDefault(host_resolver.get())); + storage_->set_http_server_properties( + make_scoped_ptr(new net::HttpServerPropertiesImpl())); + + base::FilePath cache_path = base_path_.Append(FILE_PATH_LITERAL("Cache")); + scoped_ptr<net::HttpCache::DefaultBackend> main_backend( + new net::HttpCache::DefaultBackend( + net::DISK_CACHE, net::CACHE_BACKEND_DEFAULT, cache_path, 0, + content::BrowserThread::GetMessageLoopProxyForThread( + content::BrowserThread::CACHE))); + + net::HttpNetworkSession::Params network_session_params; + network_session_params.cert_verifier = + url_request_context_->cert_verifier(); + network_session_params.transport_security_state = + url_request_context_->transport_security_state(); + network_session_params.channel_id_service = + url_request_context_->channel_id_service(); + network_session_params.proxy_service = + url_request_context_->proxy_service(); + network_session_params.ssl_config_service = + url_request_context_->ssl_config_service(); + network_session_params.http_auth_handler_factory = + url_request_context_->http_auth_handler_factory(); + network_session_params.network_delegate = network_delegate_.get(); + network_session_params.http_server_properties = + url_request_context_->http_server_properties(); + network_session_params.net_log = url_request_context_->net_log(); + network_session_params.ignore_certificate_errors = + ignore_certificate_errors_; + if (command_line.HasSwitch(switches::kHostResolverRules)) { + scoped_ptr<net::MappedHostResolver> mapped_host_resolver( + new net::MappedHostResolver(std::move(host_resolver))); + mapped_host_resolver->SetRulesFromString( + command_line.GetSwitchValueASCII(switches::kHostResolverRules)); + host_resolver = std::move(mapped_host_resolver); + } + + // Give |storage_| ownership at the end in case it's |mapped_host_resolver|. + storage_->set_host_resolver(std::move(host_resolver)); + network_session_params.host_resolver = + url_request_context_->host_resolver(); + + storage_->set_http_network_session( + make_scoped_ptr(new net::HttpNetworkSession(network_session_params))); + storage_->set_http_transaction_factory(make_scoped_ptr(new net::HttpCache( + storage_->http_network_session(), std::move(main_backend), + true /* set_up_quic_server_info */))); + + scoped_ptr<net::URLRequestJobFactoryImpl> job_factory( + new net::URLRequestJobFactoryImpl()); + + InstallProtocolHandlers(job_factory.get(), &protocol_handlers_); + bool set_protocol = job_factory->SetProtocolHandler( + url::kDataScheme, make_scoped_ptr(new net::DataProtocolHandler)); + DCHECK(set_protocol); + set_protocol = job_factory->SetProtocolHandler( + url::kFileScheme, + make_scoped_ptr(new net::FileProtocolHandler( + content::BrowserThread::GetBlockingPool() + ->GetTaskRunnerWithShutdownBehavior( + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)))); + DCHECK(set_protocol); + + // Set up interceptors in the reverse order so that the last inceptor is at + // the end of the linked list of job factories. + scoped_ptr<net::URLRequestJobFactory> top_job_factory = + std::move(job_factory); + for (auto i = request_interceptors_.rbegin(); + i != request_interceptors_.rend(); ++i) { + top_job_factory.reset(new net::URLRequestInterceptingJobFactory( + std::move(top_job_factory), make_scoped_ptr(*i))); + } + request_interceptors_.weak_clear(); + // Save the head of the job factory list at storage_. + storage_->set_job_factory(std::move(top_job_factory)); + } + + return url_request_context_.get(); +} + +scoped_refptr<base::SingleThreadTaskRunner> +HeadlessURLRequestContextGetter::GetNetworkTaskRunner() const { + return content::BrowserThread::GetMessageLoopProxyForThread( + content::BrowserThread::IO); +} + +net::HostResolver* HeadlessURLRequestContextGetter::host_resolver() const { + return url_request_context_->host_resolver(); +} + +} // namespace headless diff --git a/headless/lib/browser/headless_url_request_context_getter.h b/headless/lib/browser/headless_url_request_context_getter.h new file mode 100644 index 0000000..cfd927b --- /dev/null +++ b/headless/lib/browser/headless_url_request_context_getter.h @@ -0,0 +1,81 @@ +// Copyright 2015 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 HEADLESS_LIB_BROWSER_HEADLESS_URL_REQUEST_CONTEXT_GETTER_H_ +#define HEADLESS_LIB_BROWSER_HEADLESS_URL_REQUEST_CONTEXT_GETTER_H_ + +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "content/public/browser/content_browser_client.h" +#include "headless/public/headless_browser.h" +#include "net/proxy/proxy_config_service.h" +#include "net/url_request/url_request_context_getter.h" +#include "net/url_request/url_request_job_factory.h" + +namespace base { +class MessageLoop; +} + +namespace net { +class HostResolver; +class MappedHostResolver; +class NetworkDelegate; +class NetLog; +class ProxyConfigService; +class ProxyService; +class URLRequestContextStorage; +} + +namespace headless { + +class HeadlessURLRequestContextGetter : public net::URLRequestContextGetter { + public: + HeadlessURLRequestContextGetter( + bool ignore_certificate_errors, + const base::FilePath& base_path, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> file_task_runner, + content::ProtocolHandlerMap* protocol_handlers, + content::URLRequestInterceptorScopedVector request_interceptors, + net::NetLog* net_log, + const HeadlessBrowser::Options& options); + + // net::URLRequestContextGetter implementation: + net::URLRequestContext* GetURLRequestContext() override; + scoped_refptr<base::SingleThreadTaskRunner> GetNetworkTaskRunner() + const override; + + net::HostResolver* host_resolver() const; + + protected: + ~HeadlessURLRequestContextGetter() override; + + scoped_ptr<net::NetworkDelegate> CreateNetworkDelegate(); + scoped_ptr<net::ProxyConfigService> GetProxyConfigService(); + scoped_ptr<net::ProxyService> GetProxyService(); + + private: + bool ignore_certificate_errors_; + base::FilePath base_path_; + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; + scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_; + net::NetLog* net_log_; + HeadlessBrowser::Options options_; + + scoped_ptr<net::ProxyConfigService> proxy_config_service_; + scoped_ptr<net::NetworkDelegate> network_delegate_; + scoped_ptr<net::URLRequestContextStorage> storage_; + scoped_ptr<net::URLRequestContext> url_request_context_; + content::ProtocolHandlerMap protocol_handlers_; + content::URLRequestInterceptorScopedVector request_interceptors_; + + DISALLOW_COPY_AND_ASSIGN(HeadlessURLRequestContextGetter); +}; + +} // namespace headless + +#endif // HEADLESS_LIB_BROWSER_HEADLESS_URL_REQUEST_CONTEXT_GETTER_H_ diff --git a/headless/lib/browser/headless_web_contents_impl.cc b/headless/lib/browser/headless_web_contents_impl.cc new file mode 100644 index 0000000..869b2b1 --- /dev/null +++ b/headless/lib/browser/headless_web_contents_impl.cc @@ -0,0 +1,104 @@ +// Copyright 2015 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 "headless/lib/browser/headless_web_contents_impl.h" + +#include "base/bind.h" +#include "base/memory/weak_ptr.h" +#include "base/trace_event/trace_event.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/render_widget_host_view.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_delegate.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/common/bindings_policy.h" +#include "content/public/common/service_registry.h" +#include "content/public/renderer/render_frame.h" +#include "ui/aura/window.h" + +namespace headless { + +class WebContentsObserverAdapter : public content::WebContentsObserver { + public: + WebContentsObserverAdapter(content::WebContents* web_contents, + HeadlessWebContents::Observer* observer) + : content::WebContentsObserver(web_contents), observer_(observer) {} + + ~WebContentsObserverAdapter() override {} + + void DocumentOnLoadCompletedInMainFrame() override { + observer_->DocumentOnLoadCompletedInMainFrame(); + } + + private: + HeadlessWebContents::Observer* observer_; // Not owned. + + DISALLOW_COPY_AND_ASSIGN(WebContentsObserverAdapter); +}; + +class HeadlessWebContentsImpl::Delegate : public content::WebContentsDelegate { + public: + Delegate() {} + + private: + DISALLOW_COPY_AND_ASSIGN(Delegate); +}; + +HeadlessWebContentsImpl::HeadlessWebContentsImpl( + content::BrowserContext* browser_context, + aura::Window* parent_window, + const gfx::Size& initial_size) + : web_contents_delegate_(new HeadlessWebContentsImpl::Delegate()) { + content::WebContents::CreateParams create_params(browser_context, nullptr); + create_params.initial_size = initial_size; + + web_contents_.reset(content::WebContents::Create(create_params)); + web_contents_->SetDelegate(web_contents_delegate_.get()); + + aura::Window* contents = web_contents_->GetNativeView(); + DCHECK(!parent_window->Contains(contents)); + parent_window->AddChild(contents); + contents->Show(); + + contents->SetBounds(gfx::Rect(initial_size)); + content::RenderWidgetHostView* host_view = + web_contents_->GetRenderWidgetHostView(); + if (host_view) + host_view->SetSize(initial_size); +} + +HeadlessWebContentsImpl::~HeadlessWebContentsImpl() { + web_contents_->Close(); +} + +bool HeadlessWebContentsImpl::OpenURL(const GURL& url) { + if (!url.is_valid()) + return false; + content::NavigationController::LoadURLParams params(url); + params.transition_type = ui::PageTransitionFromInt( + ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR); + web_contents_->GetController().LoadURLWithParams(params); + web_contents_->Focus(); + return true; +} + +void HeadlessWebContentsImpl::AddObserver(Observer* observer) { + DCHECK(observer_map_.find(observer) == observer_map_.end()); + observer_map_[observer] = make_scoped_ptr( + new WebContentsObserverAdapter(web_contents_.get(), observer)); +} + +void HeadlessWebContentsImpl::RemoveObserver(Observer* observer) { + ObserverMap::iterator it = observer_map_.find(observer); + DCHECK(it != observer_map_.end()); + observer_map_.erase(it); +} + +content::WebContents* HeadlessWebContentsImpl::web_contents() const { + return web_contents_.get(); +} + +} // namespace headless diff --git a/headless/lib/browser/headless_web_contents_impl.h b/headless/lib/browser/headless_web_contents_impl.h new file mode 100644 index 0000000..b272bfe --- /dev/null +++ b/headless/lib/browser/headless_web_contents_impl.h @@ -0,0 +1,60 @@ +// Copyright 2015 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 HEADLESS_LIB_BROWSER_HEADLESS_WEB_CONTENTS_IMPL_H_ +#define HEADLESS_LIB_BROWSER_HEADLESS_WEB_CONTENTS_IMPL_H_ + +#include "headless/public/headless_web_contents.h" + +#include <unordered_map> + +namespace aura { +class Window; +} + +namespace content { +class WebContents; +class BrowserContext; +} + +namespace gfx { +class Size; +} + +namespace headless { +class WebContentsObserverAdapter; + +class HeadlessWebContentsImpl : public HeadlessWebContents { + public: + ~HeadlessWebContentsImpl() override; + + // HeadlessWebContents implementation: + bool OpenURL(const GURL& url) override; + void AddObserver(Observer* observer) override; + void RemoveObserver(Observer* observer) override; + + content::WebContents* web_contents() const; + + private: + friend class HeadlessBrowserImpl; + + HeadlessWebContentsImpl(content::BrowserContext* context, + aura::Window* parent_window, + const gfx::Size& initial_size); + + class Delegate; + scoped_ptr<Delegate> web_contents_delegate_; + scoped_ptr<content::WebContents> web_contents_; + + using ObserverMap = + std::unordered_map<HeadlessWebContents::Observer*, + scoped_ptr<WebContentsObserverAdapter>>; + ObserverMap observer_map_; + + DISALLOW_COPY_AND_ASSIGN(HeadlessWebContentsImpl); +}; + +} // namespace headless + +#endif // HEADLESS_LIB_BROWSER_HEADLESS_WEB_CONTENTS_IMPL_H_ diff --git a/headless/lib/headless_browser_browsertest.cc b/headless/lib/headless_browser_browsertest.cc new file mode 100644 index 0000000..ffcd39b --- /dev/null +++ b/headless/lib/headless_browser_browsertest.cc @@ -0,0 +1,22 @@ +// Copyright 2016 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 "content/public/test/browser_test.h" +#include "headless/public/headless_browser.h" +#include "headless/public/headless_web_contents.h" +#include "headless/test/headless_browser_test.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/size.h" + +namespace headless { + +IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, CreateAndDestroyWebContents) { + scoped_ptr<HeadlessWebContents> web_contents = + browser()->CreateWebContents(gfx::Size(800, 600)); + EXPECT_TRUE(web_contents); + // TODO(skyostil): Verify viewport dimensions once we can. + web_contents.reset(); +} + +} // namespace headless diff --git a/headless/lib/headless_content_client.cc b/headless/lib/headless_content_client.cc new file mode 100644 index 0000000..f6c96e5 --- /dev/null +++ b/headless/lib/headless_content_client.cc @@ -0,0 +1,42 @@ +// Copyright 2015 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 "headless/lib/headless_content_client.h" + +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" + +namespace headless { + +HeadlessContentClient::HeadlessContentClient( + const HeadlessBrowser::Options& options) + : options_(options) {} + +HeadlessContentClient::~HeadlessContentClient() {} + +std::string HeadlessContentClient::GetUserAgent() const { + return options_.user_agent; +} + +base::string16 HeadlessContentClient::GetLocalizedString(int message_id) const { + return l10n_util::GetStringUTF16(message_id); +} + +base::StringPiece HeadlessContentClient::GetDataResource( + int resource_id, + ui::ScaleFactor scale_factor) const { + return ResourceBundle::GetSharedInstance().GetRawDataResourceForScale( + resource_id, scale_factor); +} + +base::RefCountedStaticMemory* HeadlessContentClient::GetDataResourceBytes( + int resource_id) const { + return ResourceBundle::GetSharedInstance().LoadDataResourceBytes(resource_id); +} + +gfx::Image& HeadlessContentClient::GetNativeImageNamed(int resource_id) const { + return ResourceBundle::GetSharedInstance().GetNativeImageNamed(resource_id); +} + +} // namespace headless diff --git a/headless/lib/headless_content_client.h b/headless/lib/headless_content_client.h new file mode 100644 index 0000000..2fc8163 --- /dev/null +++ b/headless/lib/headless_content_client.h @@ -0,0 +1,36 @@ +// Copyright 2015 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 HEADLESS_LIB_HEADLESS_CONTENT_CLIENT_H_ +#define HEADLESS_LIB_HEADLESS_CONTENT_CLIENT_H_ + +#include "content/public/common/content_client.h" +#include "headless/public/headless_browser.h" + +namespace headless { + +class HeadlessContentClient : public content::ContentClient { + public: + explicit HeadlessContentClient(const HeadlessBrowser::Options& options); + ~HeadlessContentClient() override; + + // content::ContentClient implementation: + std::string GetUserAgent() const override; + base::string16 GetLocalizedString(int message_id) const override; + base::StringPiece GetDataResource( + int resource_id, + ui::ScaleFactor scale_factor) const override; + base::RefCountedStaticMemory* GetDataResourceBytes( + int resource_id) const override; + gfx::Image& GetNativeImageNamed(int resource_id) const override; + + private: + HeadlessBrowser::Options options_; + + DISALLOW_COPY_AND_ASSIGN(HeadlessContentClient); +}; + +} // namespace headless + +#endif // HEADLESS_LIB_HEADLESS_CONTENT_CLIENT_H_ diff --git a/headless/lib/headless_content_main_delegate.cc b/headless/lib/headless_content_main_delegate.cc new file mode 100644 index 0000000..64e91c0 --- /dev/null +++ b/headless/lib/headless_content_main_delegate.cc @@ -0,0 +1,124 @@ +// Copyright 2015 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 "headless/lib/headless_content_main_delegate.h" + +#include "base/command_line.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/trace_event/trace_event.h" +#include "content/public/browser/browser_main_runner.h" +#include "content/public/common/content_switches.h" +#include "headless/lib/browser/headless_browser_impl.h" +#include "headless/lib/browser/headless_content_browser_client.h" +#include "headless/lib/renderer/headless_content_renderer_client.h" +#include "headless/lib/utility/headless_content_utility_client.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/ozone/public/ozone_switches.h" + +namespace headless { +namespace { +// Keep in sync with content/common/content_constants_internal.h. +// TODO(skyostil): Add a tracing test for this. +const int kTraceEventBrowserProcessSortIndex = -6; + +HeadlessContentMainDelegate* g_current_headless_content_main_delegate = nullptr; +} // namespace + +HeadlessContentMainDelegate::HeadlessContentMainDelegate( + scoped_ptr<HeadlessBrowserImpl> browser) + : content_client_(browser->options()), browser_(std::move(browser)) { + DCHECK(!g_current_headless_content_main_delegate); + g_current_headless_content_main_delegate = this; +} + +HeadlessContentMainDelegate::~HeadlessContentMainDelegate() { + DCHECK(g_current_headless_content_main_delegate == this); + g_current_headless_content_main_delegate = nullptr; +} + +bool HeadlessContentMainDelegate::BasicStartupComplete(int* exit_code) { + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + + command_line->AppendSwitch(switches::kNoSandbox); + command_line->AppendSwitch(switches::kSingleProcess); + + // The headless backend is automatically chosen for a headless build, but also + // adding it here allows us to run in a non-headless build too. + command_line->AppendSwitchASCII(switches::kOzonePlatform, "headless"); + + // TODO(skyostil): Investigate using Mesa/SwiftShader for output. + command_line->AppendSwitch(switches::kDisableGpu); + + SetContentClient(&content_client_); + return false; +} + +void HeadlessContentMainDelegate::PreSandboxStartup() { + InitializeResourceBundle(); +} + +int HeadlessContentMainDelegate::RunProcess( + const std::string& process_type, + const content::MainFunctionParams& main_function_params) { + if (!process_type.empty()) + return -1; + + base::trace_event::TraceLog::GetInstance()->SetProcessName("HeadlessBrowser"); + base::trace_event::TraceLog::GetInstance()->SetProcessSortIndex( + kTraceEventBrowserProcessSortIndex); + + scoped_ptr<content::BrowserMainRunner> browser_runner( + content::BrowserMainRunner::Create()); + + int exit_code = browser_runner->Initialize(main_function_params); + DCHECK_LT(exit_code, 0) << "content::BrowserMainRunner::Initialize failed in " + "HeadlessContentMainDelegate::RunProcess"; + + browser_->RunOnStartCallback(); + browser_runner->Run(); + browser_.reset(); + browser_runner->Shutdown(); + + // Return value >=0 here to disable calling content::BrowserMain. + return 0; +} + +void HeadlessContentMainDelegate::ZygoteForked() { + // TODO(skyostil): Disable the zygote host. +} + +// static +HeadlessContentMainDelegate* HeadlessContentMainDelegate::GetInstance() { + return g_current_headless_content_main_delegate; +} + +// static +void HeadlessContentMainDelegate::InitializeResourceBundle() { + base::FilePath pak_file; + bool result = PathService::Get(base::DIR_MODULE, &pak_file); + DCHECK(result); + pak_file = pak_file.Append(FILE_PATH_LITERAL("headless_lib.pak")); + ui::ResourceBundle::InitSharedInstanceWithPakPath(pak_file); +} + +content::ContentBrowserClient* +HeadlessContentMainDelegate::CreateContentBrowserClient() { + browser_client_.reset(new HeadlessContentBrowserClient(browser_.get())); + return browser_client_.get(); +} + +content::ContentRendererClient* +HeadlessContentMainDelegate::CreateContentRendererClient() { + renderer_client_.reset(new HeadlessContentRendererClient); + return renderer_client_.get(); +} + +content::ContentUtilityClient* +HeadlessContentMainDelegate::CreateContentUtilityClient() { + utility_client_.reset(new HeadlessContentUtilityClient); + return utility_client_.get(); +} + +} // namespace headless diff --git a/headless/lib/headless_content_main_delegate.h b/headless/lib/headless_content_main_delegate.h new file mode 100644 index 0000000..b829d0e --- /dev/null +++ b/headless/lib/headless_content_main_delegate.h @@ -0,0 +1,62 @@ +// Copyright 2015 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 HEADLESS_LIB_HEADLESS_CONTENT_MAIN_DELEGATE_H_ +#define HEADLESS_LIB_HEADLESS_CONTENT_MAIN_DELEGATE_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "content/public/app/content_main_delegate.h" +#include "headless/lib/headless_content_client.h" + +namespace content { +class BrowserContext; +} + +namespace headless { + +class HeadlessBrowserImpl; +class HeadlessContentBrowserClient; +class HeadlessContentUtilityClient; +class HeadlessContentRendererClient; + +class HeadlessContentMainDelegate : public content::ContentMainDelegate { + public: + explicit HeadlessContentMainDelegate(scoped_ptr<HeadlessBrowserImpl> browser); + ~HeadlessContentMainDelegate() override; + + // content::ContentMainDelegate implementation: + bool BasicStartupComplete(int* exit_code) override; + void PreSandboxStartup() override; + int RunProcess( + const std::string& process_type, + const content::MainFunctionParams& main_function_params) override; + void ZygoteForked() override; + content::ContentBrowserClient* CreateContentBrowserClient() override; + content::ContentRendererClient* CreateContentRendererClient() override; + content::ContentUtilityClient* CreateContentUtilityClient() override; + + HeadlessBrowserImpl* browser() const { return browser_.get(); } + + private: + friend class HeadlessBrowserTest; + + static void InitializeResourceBundle(); + + static HeadlessContentMainDelegate* GetInstance(); + + scoped_ptr<HeadlessContentBrowserClient> browser_client_; + scoped_ptr<HeadlessContentRendererClient> renderer_client_; + scoped_ptr<HeadlessContentUtilityClient> utility_client_; + HeadlessContentClient content_client_; + + scoped_ptr<HeadlessBrowserImpl> browser_; + + DISALLOW_COPY_AND_ASSIGN(HeadlessContentMainDelegate); +}; + +} // namespace headless + +#endif // HEADLESS_LIB_HEADLESS_CONTENT_MAIN_DELEGATE_H_ diff --git a/headless/lib/headless_web_contents_browsertest.cc b/headless/lib/headless_web_contents_browsertest.cc new file mode 100644 index 0000000..5771b69 --- /dev/null +++ b/headless/lib/headless_web_contents_browsertest.cc @@ -0,0 +1,58 @@ +// Copyright 2016 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 "base/run_loop.h" +#include "content/public/test/browser_test.h" +#include "headless/public/headless_browser.h" +#include "headless/public/headless_web_contents.h" +#include "headless/test/headless_browser_test.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/size.h" +#include "url/gurl.h" + +namespace headless { + +class HeadlessWebContentsTest : public HeadlessBrowserTest {}; + +class WaitForNavigationObserver : public HeadlessWebContents::Observer { + public: + WaitForNavigationObserver(base::RunLoop* run_loop, + HeadlessWebContents* web_contents) + : run_loop_(run_loop), web_contents_(web_contents) { + web_contents_->AddObserver(this); + } + + ~WaitForNavigationObserver() override { web_contents_->RemoveObserver(this); } + + void DocumentOnLoadCompletedInMainFrame() override { run_loop_->Quit(); } + + private: + base::RunLoop* run_loop_; // Not owned. + HeadlessWebContents* web_contents_; // Not owned. + + DISALLOW_COPY_AND_ASSIGN(WaitForNavigationObserver); +}; + +IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, Navigation) { + EXPECT_TRUE(embedded_test_server()->Start()); + scoped_ptr<HeadlessWebContents> web_contents = + browser()->CreateWebContents(gfx::Size(800, 600)); + + base::RunLoop run_loop; + base::MessageLoop::ScopedNestableTaskAllower nestable_allower( + base::MessageLoop::current()); + WaitForNavigationObserver observer(&run_loop, web_contents.get()); + + web_contents->OpenURL(embedded_test_server()->GetURL("/hello.html")); + run_loop.Run(); +} + +IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, NavigationWithBadURL) { + scoped_ptr<HeadlessWebContents> web_contents = + browser()->CreateWebContents(gfx::Size(800, 600)); + GURL bad_url("not_valid"); + EXPECT_FALSE(web_contents->OpenURL(bad_url)); +} + +} // namespace headless diff --git a/headless/lib/renderer/headless_content_renderer_client.cc b/headless/lib/renderer/headless_content_renderer_client.cc new file mode 100644 index 0000000..5347d60 --- /dev/null +++ b/headless/lib/renderer/headless_content_renderer_client.cc @@ -0,0 +1,13 @@ +// Copyright 2015 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 "headless/lib/renderer/headless_content_renderer_client.h" + +namespace headless { + +HeadlessContentRendererClient::HeadlessContentRendererClient() {} + +HeadlessContentRendererClient::~HeadlessContentRendererClient() {} + +} // namespace headless diff --git a/headless/lib/renderer/headless_content_renderer_client.h b/headless/lib/renderer/headless_content_renderer_client.h new file mode 100644 index 0000000..a10ff81 --- /dev/null +++ b/headless/lib/renderer/headless_content_renderer_client.h @@ -0,0 +1,22 @@ +// Copyright 2015 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 HEADLESS_LIB_RENDERER_HEADLESS_CONTENT_RENDERER_CLIENT_H_ +#define HEADLESS_LIB_RENDERER_HEADLESS_CONTENT_RENDERER_CLIENT_H_ + +#include "content/public/renderer/content_renderer_client.h" + +namespace headless { + +class HeadlessContentRendererClient : public content::ContentRendererClient { + public: + HeadlessContentRendererClient(); + ~HeadlessContentRendererClient() override; + + DISALLOW_COPY_AND_ASSIGN(HeadlessContentRendererClient); +}; + +} // namespace headless + +#endif // HEADLESS_LIB_RENDERER_HEADLESS_CONTENT_RENDERER_CLIENT_H_ diff --git a/headless/lib/utility/headless_content_utility_client.cc b/headless/lib/utility/headless_content_utility_client.cc new file mode 100644 index 0000000..952e748 --- /dev/null +++ b/headless/lib/utility/headless_content_utility_client.cc @@ -0,0 +1,13 @@ +// Copyright 2015 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 "headless/lib/utility/headless_content_utility_client.h" + +namespace headless { + +HeadlessContentUtilityClient::HeadlessContentUtilityClient() {} + +HeadlessContentUtilityClient::~HeadlessContentUtilityClient() {} + +} // namespace headless diff --git a/headless/lib/utility/headless_content_utility_client.h b/headless/lib/utility/headless_content_utility_client.h new file mode 100644 index 0000000..0a522f2 --- /dev/null +++ b/headless/lib/utility/headless_content_utility_client.h @@ -0,0 +1,22 @@ +// Copyright 2015 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 HEADLESS_LIB_UTILITY_HEADLESS_CONTENT_UTILITY_CLIENT_H_ +#define HEADLESS_LIB_UTILITY_HEADLESS_CONTENT_UTILITY_CLIENT_H_ + +#include "content/public/utility/content_utility_client.h" + +namespace headless { + +class HeadlessContentUtilityClient : public content::ContentUtilityClient { + public: + HeadlessContentUtilityClient(); + ~HeadlessContentUtilityClient() override; + + DISALLOW_COPY_AND_ASSIGN(HeadlessContentUtilityClient); +}; + +} // namespace headless + +#endif // HEADLESS_LIB_UTILITY_HEADLESS_CONTENT_UTILITY_CLIENT_H_ diff --git a/headless/public/headless_browser.cc b/headless/public/headless_browser.cc index cb8a855..550d716 100644 --- a/headless/public/headless_browser.cc +++ b/headless/public/headless_browser.cc @@ -2,15 +2,25 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "content/public/common/user_agent.h" #include "headless/public/headless_browser.h" +#include "net/url_request/url_request_context_getter.h" using Options = headless::HeadlessBrowser::Options; using Builder = headless::HeadlessBrowser::Options::Builder; namespace headless { +// Product name for building the default user agent string. +namespace { +const char kProductName[] = "HeadlessChrome"; +} + Options::Options(int argc, const char** argv) - : argc(argc), argv(argv), devtools_http_port(kInvalidPort) {} + : argc(argc), + argv(argv), + user_agent(content::BuildUserAgentFromProduct(kProductName)), + message_pump(nullptr) {} Options::~Options() {} @@ -23,14 +33,13 @@ Builder& Builder::SetUserAgent(const std::string& user_agent) { return *this; } -Builder& Builder::EnableDevToolsServer(int port) { - options_.devtools_http_port = port; +Builder& Builder::EnableDevToolsServer(const net::IPEndPoint& endpoint) { + options_.devtools_endpoint = endpoint; return *this; } -Builder& Builder::SetURLRequestContextGetter( - scoped_refptr<net::URLRequestContextGetter> url_request_context_getter) { - options_.url_request_context_getter = url_request_context_getter; +Builder& Builder::SetMessagePump(base::MessagePump* message_pump) { + options_.message_pump = message_pump; return *this; } diff --git a/headless/public/headless_browser.h b/headless/public/headless_browser.h index a94a90c..8302c0f 100644 --- a/headless/public/headless_browser.h +++ b/headless/public/headless_browser.h @@ -5,65 +5,56 @@ #ifndef HEADLESS_PUBLIC_HEADLESS_BROWSER_H_ #define HEADLESS_PUBLIC_HEADLESS_BROWSER_H_ +#include <string> + #include "base/callback.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "headless/public/headless_export.h" +#include "net/base/ip_endpoint.h" namespace base { +class MessagePump; class SingleThreadTaskRunner; - -namespace trace_event { -class TraceConfig; -} } namespace gfx { class Size; } -namespace net { -class URLRequestContextGetter; -} - namespace headless { -class WebContents; +class HeadlessWebContents; +// This class represents the global headless browser instance. To get a pointer +// to one, call |HeadlessBrowserMain| to initiate the browser main loop. An +// instance of |HeadlessBrowser| will be passed to the callback given to that +// function. class HEADLESS_EXPORT HeadlessBrowser { public: - static HeadlessBrowser* Get(); - struct Options; - // Main routine for running browser. - // Takes command line args and callback to run as soon as browser starts. - static int Run( - const Options& options, - const base::Callback<void(HeadlessBrowser*)>& on_browser_start_callback); - - // Create a new browser tab. - virtual scoped_ptr<WebContents> CreateWebContents(const gfx::Size& size) = 0; - - virtual scoped_refptr<base::SingleThreadTaskRunner> BrowserMainThread() = 0; - virtual scoped_refptr<base::SingleThreadTaskRunner> RendererMainThread() = 0; + // Create a new browser tab. |size| is in physical pixels. + virtual scoped_ptr<HeadlessWebContents> CreateWebContents( + const gfx::Size& size) = 0; - // Requests browser to stop as soon as possible. - // |Run| will return as soon as browser stops. - virtual void Stop() = 0; + // Returns a task runner for submitting work to the browser main thread. + virtual scoped_refptr<base::SingleThreadTaskRunner> BrowserMainThread() + const = 0; - virtual void StartTracing(const base::trace_event::TraceConfig& trace_config, - const base::Closure& on_tracing_started) = 0; - virtual void StopTracing(const std::string& log_file_name, - const base::Closure& on_tracing_stopped) = 0; + // Requests browser to stop as soon as possible. |Run| will return as soon as + // browser stops. + virtual void Shutdown() = 0; protected: + HeadlessBrowser() {} virtual ~HeadlessBrowser() {} private: DISALLOW_COPY_AND_ASSIGN(HeadlessBrowser); }; +// Embedding API overrides for the headless browser. struct HeadlessBrowser::Options { ~Options(); @@ -76,20 +67,10 @@ struct HeadlessBrowser::Options { std::string user_agent; std::string navigator_platform; - static const int kInvalidPort = -1; - // If not null, create start devtools for remote debugging - // on specified port. - int devtools_http_port; + net::IPEndPoint devtools_endpoint; - // Optional URLRequestContextGetter for customizing network stack. - // Allows overriding: - // - Cookie storage - // - HTTP cache - // - SSL config - // - Proxy service - scoped_refptr<net::URLRequestContextGetter> url_request_context_getter; - - scoped_ptr<base::MessagePump> message_pump; + // Optional message pump that overrides the default. Must outlive the browser. + base::MessagePump* message_pump; private: Options(int argc, const char** argv); @@ -101,9 +82,8 @@ class HeadlessBrowser::Options::Builder { ~Builder(); Builder& SetUserAgent(const std::string& user_agent); - Builder& EnableDevToolsServer(int port); - Builder& SetURLRequestContextGetter( - scoped_refptr<net::URLRequestContextGetter> url_request_context_getter); + Builder& EnableDevToolsServer(const net::IPEndPoint& endpoint); + Builder& SetMessagePump(base::MessagePump* message_pump); Options Build(); @@ -113,6 +93,15 @@ class HeadlessBrowser::Options::Builder { DISALLOW_COPY_AND_ASSIGN(Builder); }; +// Main entry point for running the headless browser. This function constructs +// the headless browser instance, passing it to the given +// |on_browser_start_callback| callback. Note that since this function executes +// the main loop, it will only return after HeadlessBrowser::Shutdown() is +// called, returning the exit code for the process. +int HeadlessBrowserMain( + const HeadlessBrowser::Options& options, + const base::Callback<void(HeadlessBrowser*)>& on_browser_start_callback); + } // namespace headless #endif // HEADLESS_PUBLIC_HEADLESS_BROWSER_H_ diff --git a/headless/public/headless_web_contents.h b/headless/public/headless_web_contents.h new file mode 100644 index 0000000..2db82e9 --- /dev/null +++ b/headless/public/headless_web_contents.h @@ -0,0 +1,53 @@ +// Copyright 2015 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 HEADLESS_PUBLIC_HEADLESS_WEB_CONTENTS_H_ +#define HEADLESS_PUBLIC_HEADLESS_WEB_CONTENTS_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "headless/public/headless_export.h" +#include "url/gurl.h" + +namespace headless { + +// Class representing contents of a browser tab. Should be accessed from browser +// main thread. +class HEADLESS_EXPORT HeadlessWebContents { + public: + virtual ~HeadlessWebContents() {} + + // TODO(skyostil): Replace this with an equivalent client API. + class Observer { + public: + // Will be called on browser thread. + virtual void DocumentOnLoadCompletedInMainFrame() = 0; + + protected: + Observer() {} + virtual ~Observer() {} + + private: + DISALLOW_COPY_AND_ASSIGN(Observer); + }; + + // TODO(skyostil): Replace this with an equivalent client API. + virtual bool OpenURL(const GURL& url) = 0; + + // Add or remove an observer to receive events from this WebContents. + // |observer| must outlive this class or be removed prior to being destroyed. + virtual void AddObserver(Observer* observer) = 0; + virtual void RemoveObserver(Observer* observer) = 0; + + private: + friend class HeadlessWebContentsImpl; + HeadlessWebContents() {} + + DISALLOW_COPY_AND_ASSIGN(HeadlessWebContents); +}; + +} // namespace headless + +#endif // HEADLESS_PUBLIC_HEADLESS_WEB_CONTENTS_H_ diff --git a/headless/public/network.h b/headless/public/network.h deleted file mode 100644 index efede2b..0000000 --- a/headless/public/network.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2015 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 HEADLESS_PUBLIC_NETWORK_H_ -#define HEADLESS_PUBLIC_NETWORK_H_ - -#include <base/macros.h> -#include <base/memory/ref_counted.h> -#include <base/memory/scoped_ptr.h> - -namespace net { -class URLRequestContextGetter; -class ClientSocketFactory; -class HttpTransactionFactory; -} - -namespace headless { - -class Network { - public: - static scoped_refptr<net::URLRequestContextGetter> - CreateURLRequestContextGetterUsingSocketFactory( - scoped_ptr<net::ClientSocketFactory> socket_factory); - - static scoped_refptr<net::URLRequestContextGetter> - CreateURLRequestContextGetterUsingHttpTransactionFactory( - scoped_ptr<net::HttpTransactionFactory> http_transaction_factory); - - private: - Network() = delete; -}; - -} // namespace headless - -#endif // HEADLESS_PUBLIC_NETWORK_H_ diff --git a/headless/public/web_contents.h b/headless/public/web_contents.h deleted file mode 100644 index 634cdb4..0000000 --- a/headless/public/web_contents.h +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2015 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 HEADLESS_PUBLIC_WEB_CONTENTS_H_ -#define HEADLESS_PUBLIC_WEB_CONTENTS_H_ - -#include "base/callback.h" -#include "base/macros.h" -#include "base/memory/scoped_ptr.h" -#include "headless/public/headless_export.h" -#include "url/gurl.h" - -class SkBitmap; - -namespace content { -class WebContents; -} - -namespace headless { -class WebFrame; - -// Class representing contents of a browser tab. -// Should be accessed from browser main thread. -class HEADLESS_EXPORT WebContents { - public: - virtual ~WebContents() {} - - class Observer { - public: - // Will be called on browser thread. - virtual void DidNavigateMainFrame() = 0; - virtual void DocumentOnLoadCompletedInMainFrame() = 0; - - virtual void OnLoadProgressChanged(double progress) = 0; - virtual void AddMessageToConsole(const std::string& message) = 0; - virtual bool ShouldSuppressDialogs(WebContents* source) = 0; - virtual void OnModalAlertDialog(const std::string& message) = 0; - virtual bool OnModalConfirmDialog(const std::string& message) = 0; - virtual std::string OnModalPromptDialog( - const std::string& message, - const std::string& default_value) = 0; - - protected: - explicit Observer(WebContents* web_contents); - virtual ~Observer(); - - private: - class ObserverImpl; - scoped_ptr<ObserverImpl> observer_; - - DISALLOW_COPY_AND_ASSIGN(Observer); - }; - - class Settings { - virtual void SetWebSecurityEnabled(bool enabled) = 0; - virtual void SetLocalStorageEnabled(bool enabled) = 0; - virtual void SetJavaScriptCanOpenWindowsAutomatically(bool enabled) = 0; - virtual void SetAllowScriptsToCloseWindows(bool enabled) = 0; - virtual void SetImagesEnabled(bool enabled) = 0; - virtual void SetJavascriptEnabled(bool enabled) = 0; - virtual void SetXSSAuditorEnabled(bool enabled) = 0; - virtual void SetDefaultTextEncoding(const std::string& encoding) = 0; - }; - - virtual Settings* GetSettings() = 0; - virtual const Settings* GetSettings() const = 0; - - virtual NavigationController* GetController() = 0; - virtual const NavigationController* GetController() const = 0; - - virtual std::string GetTitle() const = 0; - virtual const GURL& GetVisibleURL() const = 0; - virtual const GURL& GetLastCommittedURL() const = 0; - virtual bool IsLoading() const = 0; - - virtual bool SetViewportSize(const gfx::Size& size) const = 0; - - // Returns main frame for web page. Note that the returned interface should - // only be used on the renderer main thread. - virtual WebFrame* GetMainFrame() = 0; - - // Requests browser tab to navigate to given url. - virtual void OpenURL(const GURL& url) = 0; - - using ScreenshotCallback = base::Callback<void(scoped_ptr<SkBitmap>)>; - // Requests an image of web contents. - virtual void GetScreenshot(const ScreenshotCallback& callback) = 0; - - protected: - virtual content::WebContents* web_contents() = 0; - - private: - DISALLOW_COPY_AND_ASSIGN(WebContents); -}; - -} // namespace headless - -#endif // HEADLESS_PUBLIC_WEB_CONTENTS_H_ diff --git a/headless/public/web_frame.h b/headless/public/web_frame.h deleted file mode 100644 index c8e5307..0000000 --- a/headless/public/web_frame.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2015 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 HEADLESS_PUBLIC_WEB_FRAME_H_ -#define HEADLESS_PUBLIC_WEB_FRAME_H_ - -#include "base/callback.h" -#include "base/macros.h" -#include "base/values.h" -#include "headless/public/headless_export.h" - -namespace headless { - -class WebDocument; - -// Class representing a frame in a web page (e.g. main frame or an iframe). -// Should be accessed from renderer main thread. -class HEADLESS_EXPORT WebFrame { - public: - virtual ~WebFrame() {} - - using ScriptExecutionCallback = - base::Callback<void(const std::vector<scoped_ptr<base::Value>>&)>; - - // Schedule given script for execution. - virtual void ExecuteScript(const std::string& source_code) = 0; - - // Execute given script and return its result. - // Returned value will be converted to json (base::Value). - // Special effects to bear in mind: - // - Boolean will be converted to base::FundamentalValue (no surprises here). - // - Number will be converted to base::FundamentalValue. - // - Array will be converted to base::ListValue. - // Note: All non-numerical properties will be omitted - // (e.g. "array = [1, 2, 3]; array['property'] = 'value'; return array" - // will return [1, 2, 3]). - // - Object will be converted to base::DictionaryValue - // Note: Only string can be key in base::DictionaryValue, so all non-string - // properties will be omitted - // (e.g. "obj = Object(); obj['key'] = 'value'; obj[0] = 42;" will return - // {"key":"value"}). - virtual void ExecuteScriptAndReturnValue( - const std::string& source_code, - const ScriptExecutionCallback& callback) = 0; - - virtual std::string ContentAsText(size_t max_chars) const = 0; - virtual std::string ContentAsMarkup() const = 0; - virtual proto::Document ContentAsProtobuf() const = 0; - - virtual gfx::Size GetScrollOffset() const = 0; - virtual void SetScrollOffset(const gfx::Size& offset) = 0; - - virtual float GetPageScaleFactor() const = 0; - virtual void SetPageScaleFactor(float page_scale_factor) = 0; - - private: - DISALLOW_COPY_AND_ASSIGN(WebFrame); -}; - -} // namespace headless - -#endif // HEADLESS_PUBLIC_WEB_FRAME_H_ diff --git a/headless/test/data/hello.html b/headless/test/data/hello.html new file mode 100644 index 0000000..0aa881e --- /dev/null +++ b/headless/test/data/hello.html @@ -0,0 +1,2 @@ +<!doctype html> +<h1>Hello headless world!</h1> diff --git a/headless/test/headless_browser_test.cc b/headless/test/headless_browser_test.cc new file mode 100644 index 0000000..555616a --- /dev/null +++ b/headless/test/headless_browser_test.cc @@ -0,0 +1,50 @@ +// Copyright 2016 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 "headless/test/headless_browser_test.h" + +#include "base/files/file_path.h" +#include "base/message_loop/message_loop.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_process_host.h" +#include "headless/lib/browser/headless_browser_impl.h" +#include "headless/lib/headless_content_main_delegate.h" + +namespace headless { + +HeadlessBrowserTest::HeadlessBrowserTest() { + base::FilePath headless_test_data(FILE_PATH_LITERAL("headless/test/data")); + CreateTestServer(headless_test_data); +} + +HeadlessBrowserTest::~HeadlessBrowserTest() {} + +void HeadlessBrowserTest::SetUpOnMainThread() {} + +void HeadlessBrowserTest::TearDownOnMainThread() { + browser()->Shutdown(); +} + +void HeadlessBrowserTest::RunTestOnMainThreadLoop() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + + // Pump startup related events. + base::MessageLoop::current()->RunUntilIdle(); + + SetUpOnMainThread(); + RunTestOnMainThread(); + TearDownOnMainThread(); + + for (content::RenderProcessHost::iterator i( + content::RenderProcessHost::AllHostsIterator()); + !i.IsAtEnd(); i.Advance()) { + i.GetCurrentValue()->FastShutdownIfPossible(); + } +} + +HeadlessBrowser* HeadlessBrowserTest::browser() const { + return HeadlessContentMainDelegate::GetInstance()->browser(); +} + +} // namespace headless diff --git a/headless/test/headless_browser_test.h b/headless/test/headless_browser_test.h new file mode 100644 index 0000000..9b56234 --- /dev/null +++ b/headless/test/headless_browser_test.h @@ -0,0 +1,34 @@ +// Copyright 2016 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 HEADLESS_TEST_HEADLESS_BROWSER_TEST_H_ +#define HEADLESS_TEST_HEADLESS_BROWSER_TEST_H_ + +#include "content/public/test/browser_test_base.h" + +namespace headless { +class HeadlessBrowser; + +// Base class for tests which require a full instance of the headless browser. +class HeadlessBrowserTest : public content::BrowserTestBase { + protected: + HeadlessBrowserTest(); + ~HeadlessBrowserTest() override; + + // BrowserTestBase: + void RunTestOnMainThreadLoop() override; + void SetUpOnMainThread() override; + void TearDownOnMainThread() override; + + protected: + // Returns the browser for the test. + HeadlessBrowser* browser() const; + + private: + DISALLOW_COPY_AND_ASSIGN(HeadlessBrowserTest); +}; + +} // namespace headless + +#endif // HEADLESS_TEST_HEADLESS_BROWSER_TEST_H_ diff --git a/headless/test/headless_test_launcher.cc b/headless/test/headless_test_launcher.cc new file mode 100644 index 0000000..e08e297 --- /dev/null +++ b/headless/test/headless_test_launcher.cc @@ -0,0 +1,66 @@ +// Copyright 2016 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 "base/bind.h" +#include "base/macros.h" +#include "base/sys_info.h" +#include "content/public/test/content_test_suite_base.h" +#include "content/public/test/test_launcher.h" +#include "headless/lib/browser/headless_browser_impl.h" +#include "headless/lib/headless_content_main_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace headless { +namespace { + +class HeadlessBrowserImplForTest : public HeadlessBrowserImpl { + public: + explicit HeadlessBrowserImplForTest(const HeadlessBrowser::Options& options) + : HeadlessBrowserImpl(base::Bind(&HeadlessBrowserImplForTest::OnStart, + base::Unretained(this)), + options) {} + + void OnStart(HeadlessBrowser* browser) { EXPECT_EQ(this, browser); } + + private: + DISALLOW_COPY_AND_ASSIGN(HeadlessBrowserImplForTest); +}; + +class HeadlessTestLauncherDelegate : public content::TestLauncherDelegate { + public: + HeadlessTestLauncherDelegate() {} + ~HeadlessTestLauncherDelegate() override {} + + // content::TestLauncherDelegate implementation: + int RunTestSuite(int argc, char** argv) override { + return base::TestSuite(argc, argv).Run(); + } + + bool AdjustChildProcessCommandLine( + base::CommandLine* command_line, + const base::FilePath& temp_data_dir) override { + return true; + } + + protected: + content::ContentMainDelegate* CreateContentMainDelegate() override { + // TODO(skyostil): Add a way to test custom options. + HeadlessBrowser::Options::Builder options_builder(0, nullptr); + scoped_ptr<HeadlessBrowserImpl> browser( + new HeadlessBrowserImplForTest(options_builder.Build())); + return new HeadlessContentMainDelegate(std::move(browser)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(HeadlessTestLauncherDelegate); +}; + +} // namespace +} // namespace headless + +int main(int argc, char** argv) { + int default_jobs = std::max(1, base::SysInfo::NumberOfProcessors() / 2); + headless::HeadlessTestLauncherDelegate launcher_delegate; + return LaunchTests(&launcher_delegate, default_jobs, argc, argv); +} |