summaryrefslogtreecommitdiffstats
path: root/headless
diff options
context:
space:
mode:
authorskyostil <skyostil@chromium.org>2016-02-26 12:53:33 -0800
committerCommit bot <commit-bot@chromium.org>2016-02-26 20:59:19 +0000
commitfe11616891b5f57ea977c24efdf7946ed2cbf5f7 (patch)
tree2001cd7ae73c6b716ab8782ae86e578ef7e306df /headless
parent88eedb7f8965e0134502f1f471f6d9f8fc82a940 (diff)
downloadchromium_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')
-rw-r--r--headless/BUILD.gn116
-rw-r--r--headless/DEPS10
-rw-r--r--headless/README.md59
-rw-r--r--headless/app/headless_shell.cc103
-rw-r--r--headless/lib/browser/DEPS3
-rw-r--r--headless/lib/browser/headless_browser_context.cc163
-rw-r--r--headless/lib/browser/headless_browser_context.h67
-rw-r--r--headless/lib/browser/headless_browser_impl.cc93
-rw-r--r--headless/lib/browser/headless_browser_impl.h58
-rw-r--r--headless/lib/browser/headless_browser_main_parts.cc54
-rw-r--r--headless/lib/browser/headless_browser_main_parts.h42
-rw-r--r--headless/lib/browser/headless_content_browser_client.cc51
-rw-r--r--headless/lib/browser/headless_content_browser_client.h39
-rw-r--r--headless/lib/browser/headless_devtools.cc102
-rw-r--r--headless/lib/browser/headless_devtools.h24
-rw-r--r--headless/lib/browser/headless_screen.cc174
-rw-r--r--headless/lib/browser/headless_screen.h76
-rw-r--r--headless/lib/browser/headless_url_request_context_getter.cc221
-rw-r--r--headless/lib/browser/headless_url_request_context_getter.h81
-rw-r--r--headless/lib/browser/headless_web_contents_impl.cc104
-rw-r--r--headless/lib/browser/headless_web_contents_impl.h60
-rw-r--r--headless/lib/headless_browser_browsertest.cc22
-rw-r--r--headless/lib/headless_content_client.cc42
-rw-r--r--headless/lib/headless_content_client.h36
-rw-r--r--headless/lib/headless_content_main_delegate.cc124
-rw-r--r--headless/lib/headless_content_main_delegate.h62
-rw-r--r--headless/lib/headless_web_contents_browsertest.cc58
-rw-r--r--headless/lib/renderer/headless_content_renderer_client.cc13
-rw-r--r--headless/lib/renderer/headless_content_renderer_client.h22
-rw-r--r--headless/lib/utility/headless_content_utility_client.cc13
-rw-r--r--headless/lib/utility/headless_content_utility_client.h22
-rw-r--r--headless/public/headless_browser.cc21
-rw-r--r--headless/public/headless_browser.h79
-rw-r--r--headless/public/headless_web_contents.h53
-rw-r--r--headless/public/network.h36
-rw-r--r--headless/public/web_contents.h99
-rw-r--r--headless/public/web_frame.h63
-rw-r--r--headless/test/data/hello.html2
-rw-r--r--headless/test/headless_browser_test.cc50
-rw-r--r--headless/test/headless_browser_test.h34
-rw-r--r--headless/test/headless_test_launcher.cc66
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);
+}