summaryrefslogtreecommitdiff
path: root/chromium/headless
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2017-04-05 17:15:33 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2017-04-11 07:47:18 +0000
commit7324afb043a0b1e623d8e8eb906cdc53bdeb4685 (patch)
treea3fe2d74ea9c9e142c390dac4ca0e219382ace46 /chromium/headless
parent6a4cabb866f66d4128a97cdc6d9d08ce074f1247 (diff)
downloadqtwebengine-chromium-7324afb043a0b1e623d8e8eb906cdc53bdeb4685.tar.gz
BASELINE: Update Chromium to 58.0.3029.54
Change-Id: I67f57065a7afdc8e4614adb5c0230281428df4d1 Reviewed-by: Peter Varga <pvarga@inf.u-szeged.hu>
Diffstat (limited to 'chromium/headless')
-rw-r--r--chromium/headless/BUILD.gn124
-rw-r--r--chromium/headless/DEPS2
-rw-r--r--chromium/headless/OWNERS3
-rw-r--r--chromium/headless/README.md59
-rw-r--r--chromium/headless/app/headless_example.cc180
-rw-r--r--chromium/headless/app/headless_shell.cc665
-rw-r--r--chromium/headless/app/headless_shell.h98
-rw-r--r--chromium/headless/app/headless_shell_switches.cc3
-rw-r--r--chromium/headless/app/headless_shell_switches.h8
-rw-r--r--chromium/headless/app/shell_navigation_request.cc43
-rw-r--r--chromium/headless/app/shell_navigation_request.h41
-rw-r--r--chromium/headless/headless.gni8
-rw-r--r--chromium/headless/lib/browser/DEPS1
-rw-r--r--chromium/headless/lib/browser/devtools_api/client_api_generator.py13
-rw-r--r--chromium/headless/lib/browser/devtools_api/domain_cc.template4
-rw-r--r--chromium/headless/lib/browser/devtools_api/domain_h.template4
-rw-r--r--chromium/headless/lib/browser/headless_browser_context_impl.cc11
-rw-r--r--chromium/headless/lib/browser/headless_browser_context_options.cc18
-rw-r--r--chromium/headless/lib/browser/headless_browser_context_options.h5
-rw-r--r--chromium/headless/lib/browser/headless_browser_impl.cc28
-rw-r--r--chromium/headless/lib/browser/headless_browser_impl.h31
-rw-r--r--chromium/headless/lib/browser/headless_browser_impl_aura.cc56
-rw-r--r--chromium/headless/lib/browser/headless_browser_impl_mac.mm27
-rw-r--r--chromium/headless/lib/browser/headless_browser_main_parts.cc17
-rw-r--r--chromium/headless/lib/browser/headless_content_browser_client.cc111
-rw-r--r--chromium/headless/lib/browser/headless_content_browser_client.h12
-rw-r--r--chromium/headless/lib/browser/headless_devtools.cc5
-rw-r--r--chromium/headless/lib/browser/headless_focus_client.cc63
-rw-r--r--chromium/headless/lib/browser/headless_focus_client.h42
-rw-r--r--chromium/headless/lib/browser/headless_screen.cc115
-rw-r--r--chromium/headless/lib/browser/headless_screen.h28
-rw-r--r--chromium/headless/lib/browser/headless_web_contents_impl.cc29
-rw-r--r--chromium/headless/lib/browser/headless_web_contents_impl.h10
-rw-r--r--chromium/headless/lib/browser/headless_window_tree_host.cc6
-rw-r--r--chromium/headless/lib/browser/headless_window_tree_host.h6
-rw-r--r--chromium/headless/lib/embed_data.py96
-rw-r--r--chromium/headless/lib/embedder_mojo_browsertest.cc17
-rw-r--r--chromium/headless/lib/headless_browser_browsertest.cc90
-rw-r--r--chromium/headless/lib/headless_browser_context_browsertest.cc2
-rw-r--r--chromium/headless/lib/headless_content_client.cc4
-rw-r--r--chromium/headless/lib/headless_content_client.h1
-rw-r--r--chromium/headless/lib/headless_content_main_delegate.cc121
-rw-r--r--chromium/headless/lib/headless_content_main_delegate.h12
-rw-r--r--chromium/headless/lib/headless_crash_reporter_client.cc63
-rw-r--r--chromium/headless/lib/headless_crash_reporter_client.h51
-rw-r--r--chromium/headless/lib/headless_devtools_client_browsertest.cc100
-rw-r--r--chromium/headless/lib/headless_macros.h12
-rw-r--r--chromium/headless/lib/headless_web_contents_browsertest.cc39
-rw-r--r--chromium/headless/lib/util/embedded_file.h22
-rw-r--r--chromium/headless/public/domains/types_unittest.cc2
-rw-r--r--chromium/headless/public/headless_browser.cc35
-rw-r--r--chromium/headless/public/headless_browser.h21
-rw-r--r--chromium/headless/public/headless_browser_context.h13
-rw-r--r--chromium/headless/public/headless_shell.h3
-rw-r--r--chromium/headless/public/internal/value_conversions.h6
-rw-r--r--chromium/headless/public/util/deterministic_dispatcher.cc93
-rw-r--r--chromium/headless/public/util/deterministic_dispatcher.h24
-rw-r--r--chromium/headless/public/util/deterministic_dispatcher_test.cc68
-rw-r--r--chromium/headless/public/util/deterministic_http_protocol_handler.cc3
-rw-r--r--chromium/headless/public/util/dom_tree_extractor.h1
-rw-r--r--chromium/headless/public/util/dom_tree_extractor_browsertest.cc41
-rw-r--r--chromium/headless/public/util/expedited_dispatcher.cc10
-rw-r--r--chromium/headless/public/util/expedited_dispatcher.h2
-rw-r--r--chromium/headless/public/util/expedited_dispatcher_test.cc49
-rw-r--r--chromium/headless/public/util/flat_dom_tree_extractor.cc114
-rw-r--r--chromium/headless/public/util/flat_dom_tree_extractor.h89
-rw-r--r--chromium/headless/public/util/flat_dom_tree_extractor_browsertest.cc921
-rw-r--r--chromium/headless/public/util/generic_url_request_job.cc58
-rw-r--r--chromium/headless/public/util/generic_url_request_job.h24
-rw-r--r--chromium/headless/public/util/generic_url_request_job_test.cc14
-rw-r--r--chromium/headless/public/util/navigation_request.h31
-rw-r--r--chromium/headless/public/util/protocol_handler_request_id_browsertest.cc218
-rw-r--r--chromium/headless/public/util/testing/generic_url_request_mocks.cc4
-rw-r--r--chromium/headless/public/util/testing/generic_url_request_mocks.h4
-rw-r--r--chromium/headless/public/util/url_request_dispatcher.h5
-rw-r--r--chromium/headless/public/version.h.in13
76 files changed, 3716 insertions, 656 deletions
diff --git a/chromium/headless/BUILD.gn b/chromium/headless/BUILD.gn
index d6cd10639cd..bc66c58443b 100644
--- a/chromium/headless/BUILD.gn
+++ b/chromium/headless/BUILD.gn
@@ -3,6 +3,8 @@
# found in the LICENSE file.
import("//build/config/chrome_build.gni")
+import("//headless/headless.gni")
+import("//build/util/process_version.gni")
import("//mojo/public/tools/bindings/mojom.gni")
import("//testing/test.gni")
import("//tools/grit/grit_rule.gni")
@@ -10,6 +12,10 @@ import("//tools/grit/repack.gni")
config("headless_implementation") {
defines = [ "HEADLESS_IMPLEMENTATION" ]
+
+ if (headless_use_embedded_resources) {
+ defines += [ "HEADLESS_USE_EMBEDDED_RESOURCES" ]
+ }
}
group("headless") {
@@ -20,12 +26,12 @@ group("headless") {
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/components/strings/components_strings_en-US.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/devtools/devtools_resources.pak",
"$root_gen_dir/content/browser/tracing/tracing_resources.pak",
"$root_gen_dir/content/content_resources.pak",
"$root_gen_dir/headless/headless_lib_resources.pak",
@@ -60,6 +66,39 @@ repack("pak") {
output = "$root_out_dir/headless_lib.pak"
}
+action("embed_resources") {
+ # TODO(altimin): Consider zipping file here, it can reduce size up to 80%.
+ script = "//headless/lib/embed_data.py"
+
+ inputs = [
+ "$root_out_dir/headless_lib.pak",
+ ]
+
+ outputs = [
+ "$root_gen_dir/headless/embedded_resource_pak.cc",
+ "$root_gen_dir/headless/embedded_resource_pak.h",
+ ]
+
+ args = [
+ "--data_file",
+ rebase_path("$root_out_dir/headless_lib.pak"),
+ "--gendir",
+ rebase_path("$root_gen_dir"),
+ "--header_file",
+ "headless/embedded_resource_pak.h",
+ "--source_file",
+ "headless/embedded_resource_pak.cc",
+ "--namespace",
+ "headless",
+ "--variable_name",
+ "kHeadlessResourcePak",
+ ]
+
+ deps = [
+ ":pak",
+ ]
+}
+
grit("resources") {
source = "lib/resources/headless_lib_resources.grd"
outputs = [
@@ -157,6 +196,7 @@ static_library("headless_lib") {
"lib/browser/headless_browser_context_options.h",
"lib/browser/headless_browser_impl.cc",
"lib/browser/headless_browser_impl.h",
+ "lib/browser/headless_browser_impl_mac.mm",
"lib/browser/headless_browser_main_parts.cc",
"lib/browser/headless_browser_main_parts.h",
"lib/browser/headless_content_browser_client.cc",
@@ -167,18 +207,15 @@ static_library("headless_lib") {
"lib/browser/headless_devtools_client_impl.h",
"lib/browser/headless_devtools_manager_delegate.cc",
"lib/browser/headless_devtools_manager_delegate.h",
+ "lib/browser/headless_macros.h",
"lib/browser/headless_platform_event_source.cc",
"lib/browser/headless_platform_event_source.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/browser/headless_window_parenting_client.cc",
- "lib/browser/headless_window_parenting_client.h",
- "lib/browser/headless_window_tree_host.cc",
- "lib/browser/headless_window_tree_host.h",
+ "lib/headless_crash_reporter_client.cc",
+ "lib/headless_crash_reporter_client.h",
"lib/headless_content_client.cc",
"lib/headless_content_client.h",
"lib/headless_content_main_delegate.cc",
@@ -207,6 +244,8 @@ static_library("headless_lib") {
"public/util/expedited_dispatcher.h",
"public/util/generic_url_request_job.cc",
"public/util/generic_url_request_job.h",
+ "public/util/flat_dom_tree_extractor.cc",
+ "public/util/flat_dom_tree_extractor.h",
"public/util/http_url_fetcher.cc",
"public/util/http_url_fetcher.h",
"public/util/in_memory_protocol_handler.cc",
@@ -215,6 +254,7 @@ static_library("headless_lib") {
"public/util/in_memory_request_job.h",
"public/util/managed_dispatch_url_request_job.cc",
"public/util/managed_dispatch_url_request_job.h",
+ "public/util/navigation_request.h",
"public/util/testing/generic_url_request_mocks.cc",
"public/util/testing/generic_url_request_mocks.h",
"public/util/url_fetcher.cc",
@@ -224,20 +264,35 @@ static_library("headless_lib") {
"public/util/user_agent.h",
]
+ if (use_aura) {
+ sources += [
+ "lib/browser/headless_browser_impl_aura.cc",
+ "lib/browser/headless_focus_client.cc",
+ "lib/browser/headless_focus_client.h",
+ "lib/browser/headless_screen.cc",
+ "lib/browser/headless_screen.h",
+ "lib/browser/headless_window_parenting_client.cc",
+ "lib/browser/headless_window_parenting_client.h",
+ "lib/browser/headless_window_tree_host.cc",
+ "lib/browser/headless_window_tree_host.h",
+ ]
+ }
+
deps = [
":gen_devtools_client_api",
- ":pak",
+ ":version_header",
"//base",
+ "//components/crash/content/browser",
"//components/security_state/content",
"//components/security_state/core",
"//content/public/app:both",
"//content/public/browser",
+ "//content/public/child:child",
"//content/public/common",
"//content/public/common:service_names",
"//net",
"//services/service_manager/public/cpp",
"//third_party/mesa:osmesa",
- "//ui/aura",
"//ui/base",
"//ui/compositor",
"//ui/display",
@@ -245,6 +300,28 @@ static_library("headless_lib") {
"//url",
]
+ if (is_win) {
+ deps += [
+ "//build/win:default_exe_manifest",
+ "//content:sandbox_helper_win",
+ "//sandbox",
+ ]
+ }
+
+ if (!is_mac) {
+ deps += [ "//ui/aura" ]
+ }
+
+ if (headless_use_embedded_resources) {
+ deps += [ ":embed_resources" ]
+ sources += [
+ "$root_gen_dir/headless/embedded_resource_pak.cc",
+ "$root_gen_dir/headless/embedded_resource_pak.h",
+ ]
+ } else {
+ deps += [ ":pak" ]
+ }
+
if (use_ozone) {
deps += [ "//ui/ozone" ]
}
@@ -257,6 +334,7 @@ group("headless_tests") {
deps = [
":headless_browsertests",
+ ":headless_example",
":headless_unittests",
]
}
@@ -321,6 +399,8 @@ test("headless_browsertests") {
"lib/headless_devtools_client_browsertest.cc",
"lib/headless_web_contents_browsertest.cc",
"public/util/dom_tree_extractor_browsertest.cc",
+ "public/util/flat_dom_tree_extractor_browsertest.cc",
+ "public/util/protocol_handler_request_id_browsertest.cc",
"test/headless_browser_test.cc",
"test/headless_browser_test.h",
"test/headless_test_launcher.cc",
@@ -357,14 +437,19 @@ test("headless_browsertests") {
static_library("headless_shell_lib") {
sources = [
"app/headless_shell.cc",
+ "app/headless_shell.h",
"app/headless_shell_switches.cc",
"app/headless_shell_switches.h",
+ "app/shell_navigation_request.cc",
+ "app/shell_navigation_request.h",
"public/headless_shell.h",
]
deps = [
"//headless:headless_lib",
]
+
+ configs += [ ":headless_implementation" ]
}
executable("headless_shell") {
@@ -375,4 +460,25 @@ executable("headless_shell") {
deps = [
"//headless:headless_shell_lib",
]
+
+ configs += [ ":headless_implementation" ]
+}
+
+process_version("version_header") {
+ template_file = "public/version.h.in"
+ sources = [
+ "//build/util/LASTCHANGE",
+ "//chrome/VERSION",
+ ]
+ output = "$target_gen_dir/public/version.h"
+}
+
+executable("headless_example") {
+ sources = [
+ "app/headless_example.cc",
+ ]
+
+ deps = [
+ "//headless:headless_shell_lib",
+ ]
}
diff --git a/chromium/headless/DEPS b/chromium/headless/DEPS
index edf61b6ede4..2e51ac4eae3 100644
--- a/chromium/headless/DEPS
+++ b/chromium/headless/DEPS
@@ -1,4 +1,6 @@
include_rules = [
+ "+components/crash/content/app",
+ "+components/crash/content/browser",
"+content/public/app",
"+content/public/browser",
"+content/public/common",
diff --git a/chromium/headless/OWNERS b/chromium/headless/OWNERS
index c87978e7381..af4e1d8ec38 100644
--- a/chromium/headless/OWNERS
+++ b/chromium/headless/OWNERS
@@ -1,3 +1,6 @@
skyostil@chromium.org
alexclarke@chromium.org
altimin@chromium.org
+eseckler@chromium.org
+
+# TEAM: headless-dev@chromium.org
diff --git a/chromium/headless/README.md b/chromium/headless/README.md
index bf4d30149cf..eebede197f5 100644
--- a/chromium/headless/README.md
+++ b/chromium/headless/README.md
@@ -23,6 +23,37 @@ missing Mesa library.
[DevTools](https://developer.chrome.com/devtools) interface or use a tool such
as [Selenium](http://www.seleniumhq.org/) to drive the headless browser.
+## Usage from Node.js
+
+For example, the [chrome-remote-interface](https://github.com/cyrus-and/chrome-remote-interface)
+Node.js package can be used to extract a page's DOM like this:
+
+```
+const CDP = require('chrome-remote-interface');
+
+CDP((client) => {
+ // Extract used DevTools domains.
+ const {Page, Runtime} = client;
+
+ // Enable events on domains we are interested in.
+ Promise.all([
+ Page.enable()
+ ]).then(() => {
+ return Page.navigate({url: 'https://example.com'});
+ });
+
+ // Evaluate outerHTML after page has loaded.
+ Page.loadEventFired(() => {
+ Runtime.evaluate({expression: 'document.body.outerHTML'}).then((result) => {
+ console.log(result.result.value);
+ client.close();
+ });
+ });
+}).on('error', (err) => {
+ console.error('Cannot connect to browser:', err);
+});
+```
+
## Usage as a C++ library
Headless Chromium can be built as a library for embedding into a C++
@@ -30,8 +61,10 @@ application. This approach is otherwise similar to controlling the browser over
a DevTools connection, but it provides more customization points, e.g., for
networking and [mojo services](https://docs.google.com/document/d/1Fr6_DJH6OK9rG3-ibMvRPTNnHsAXPk0VzxxiuJDSK3M/edit#heading=h.qh0udvlk963d).
-Headless Shell is a sample application which demonstrates the use of the
-headless C++ API. To run it, first initialize a headless build configuration:
+[Headless Example](https://cs.chromium.org/chromium/src/headless/app/headless_example.cc)
+is a small sample application which demonstrates the use of the headless C++
+API. It loads a web page and outputs the resulting DOM. To run it, first
+initialize a headless build configuration:
```
$ mkdir -p out/Debug
@@ -39,22 +72,26 @@ $ echo 'import("//build/args/headless.gn")' > out/Debug/args.gn
$ gn gen out/Debug
```
-Then build the shell:
+Then build the example:
```
-$ ninja -C out/Debug headless_shell
+$ ninja -C out/Debug headless_example
```
-After the build completes, Headless Shell can be run with the following command:
+After the build completes, the example can be run with the following command:
```
-$ out/Debug/headless_shell https://www.google.com
+$ out/Debug/headless_example 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:
+[Headless Shell](https://cs.chromium.org/chromium/src/headless/app/headless_shell.cc)
+is a more capable headless application. For instance, it supports remote
+debugging with the [DevTools](https://developer.chrome.com/devtools) protocol.
+To do this, start the application with an argument specifying the debugging
+port:
```
+$ ninja -C out/Debug headless_shell
$ out/Debug/headless_shell --remote-debugging-port=9222 https://youtube.com
```
@@ -89,8 +126,14 @@ web pages. Its main classes are:
## Resources and Documentation
Mailing list: [headless-dev@chromium.org](https://groups.google.com/a/chromium.org/forum/#!forum/headless-dev)
+
Bug tracker: [Proj=Headless](https://bugs.chromium.org/p/chromium/issues/list?can=2&q=Proj%3DHeadless)
+[File a new bug](https://bugs.chromium.org/p/chromium/issues/entry?labels=Proj-Headless)
+
+* [BeginFrame sequence numbers + acknowledgements](https://docs.google.com/document/d/1nxaunQ0cYWxhtS6Zzfwa99nae74F7gxanbuT5JRpI6Y/edit#)
+* [Deterministic page loading for Blink](https://docs.google.com/document/d/19s2g4fPP9p9qmMZvwPX8uDGbb-39rgR9k56B4B-ueG8/edit#)
+* [Crash dumps for Headless Chrome](https://docs.google.com/document/d/1l6AGOOBLk99PaAKoZQW_DVhM8FQ6Fut27lD938CRbTM/edit)
* [Runtime headless mode for Chrome](https://docs.google.com/document/d/1aIJUzQr3eougZQp90bp4mqGr5gY6hdUice8UPa-Ys90/edit#)
* [Virtual Time in Blink](https://docs.google.com/document/d/1y9kdt_zezt7pbey6uzvt1dgklwc1ob_vy4nzo1zbqmo/edit#heading=h.tn3gd1y9ifml)
* [Headless Chrome architecture](https://docs.google.com/document/d/11zIkKkLBocofGgoTeeyibB2TZ_k7nR78v7kNelCatUE/edit)
diff --git a/chromium/headless/app/headless_example.cc b/chromium/headless/app/headless_example.cc
new file mode 100644
index 00000000000..56932ef45ba
--- /dev/null
+++ b/chromium/headless/app/headless_example.cc
@@ -0,0 +1,180 @@
+// Copyright 2017 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.
+
+// A small example application showing the use of the C++ Headless Chrome
+// library. It navigates to a web site given on the command line, waits for it
+// to load and prints out the DOM.
+//
+// Tip: start reading from the main() function below.
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/memory/weak_ptr.h"
+#include "headless/public/devtools/domains/page.h"
+#include "headless/public/devtools/domains/runtime.h"
+#include "headless/public/headless_browser.h"
+#include "headless/public/headless_devtools_client.h"
+#include "headless/public/headless_devtools_target.h"
+#include "headless/public/headless_web_contents.h"
+#include "ui/gfx/geometry/size.h"
+
+// This class contains the main application logic, i.e., waiting for a page to
+// load and printing its DOM. Note that browser initialization happens outside
+// this class.
+class HeadlessExample : public headless::HeadlessWebContents::Observer,
+ public headless::page::Observer {
+ public:
+ HeadlessExample(headless::HeadlessBrowser* browser,
+ headless::HeadlessWebContents* web_contents);
+ ~HeadlessExample() override;
+
+ // headless::HeadlessWebContents::Observer implementation:
+ void DevToolsTargetReady() override;
+
+ // headless::page::Observer implementation:
+ void OnLoadEventFired(
+ const headless::page::LoadEventFiredParams& params) override;
+
+ // Tip: Observe headless::inspector::ExperimentalObserver::OnTargetCrashed to
+ // be notified of renderer crashes.
+
+ void OnDomFetched(std::unique_ptr<headless::runtime::EvaluateResult> result);
+
+ private:
+ // The headless browser instance. Owned by the headless library. See main().
+ headless::HeadlessBrowser* browser_;
+ // Our tab. Owned by |browser_|.
+ headless::HeadlessWebContents* web_contents_;
+ // The DevTools client used to control the tab.
+ std::unique_ptr<headless::HeadlessDevToolsClient> devtools_client_;
+ // A helper for creating weak pointers to this class.
+ base::WeakPtrFactory<HeadlessExample> weak_factory_;
+};
+
+namespace {
+HeadlessExample* g_example;
+}
+
+HeadlessExample::HeadlessExample(headless::HeadlessBrowser* browser,
+ headless::HeadlessWebContents* web_contents)
+ : browser_(browser),
+ web_contents_(web_contents),
+ devtools_client_(headless::HeadlessDevToolsClient::Create()),
+ weak_factory_(this) {
+ web_contents_->AddObserver(this);
+}
+
+HeadlessExample::~HeadlessExample() {
+ // Note that we shut down the browser last, because it owns objects such as
+ // the web contents which can no longer be accessed after the browser is gone.
+ devtools_client_->GetPage()->RemoveObserver(this);
+ web_contents_->GetDevToolsTarget()->DetachClient(devtools_client_.get());
+ web_contents_->RemoveObserver(this);
+ browser_->Shutdown();
+}
+
+// This method is called when the tab is ready for DevTools inspection.
+void HeadlessExample::DevToolsTargetReady() {
+ // Attach our DevTools client to the tab so that we can send commands to it
+ // and observe events.
+ web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get());
+
+ // Start observing events from DevTools's page domain. This lets us get
+ // notified when the page has finished loading. Note that it is possible
+ // the page has already finished loading by now. See
+ // HeadlessShell::DevToolTargetReady for how to handle that case correctly.
+ devtools_client_->GetPage()->AddObserver(this);
+ devtools_client_->GetPage()->Enable();
+}
+
+void HeadlessExample::OnLoadEventFired(
+ const headless::page::LoadEventFiredParams& params) {
+ // The page has now finished loading. Let's grab a snapshot of the DOM by
+ // evaluating the outerHTML property on the body element.
+ devtools_client_->GetRuntime()->Evaluate(
+ "document.body.outerHTML",
+ base::Bind(&HeadlessExample::OnDomFetched, weak_factory_.GetWeakPtr()));
+}
+
+void HeadlessExample::OnDomFetched(
+ std::unique_ptr<headless::runtime::EvaluateResult> result) {
+ std::string dom;
+ // Make sure the evaluation succeeded before reading the result.
+ if (result->HasExceptionDetails()) {
+ LOG(ERROR) << "Failed to evaluate document.body.outerHTML: "
+ << result->GetExceptionDetails()->GetText();
+ } else if (result->GetResult()->GetValue()->GetAsString(&dom)) {
+ printf("%s\n", dom.c_str());
+ }
+
+ // Shut down the browser (see ~HeadlessExample).
+ delete g_example;
+ g_example = nullptr;
+}
+
+// This function is called by the headless library after the browser has been
+// initialized. It runs on the UI thread.
+void OnHeadlessBrowserStarted(headless::HeadlessBrowser* browser) {
+ // In order to open tabs, we first need a browser context. It corresponds to a
+ // user profile and contains things like the user's cookies, local storage,
+ // cache, etc.
+ headless::HeadlessBrowserContext::Builder context_builder =
+ browser->CreateBrowserContextBuilder();
+
+ // Here we can set options for the browser context. As an example we enable
+ // incognito mode, which makes sure profile data is not written to disk.
+ context_builder.SetIncognitoMode(true);
+
+ // Construct the context and set it as the default. The default browser
+ // context is used by the Target.createTarget() DevTools command when no other
+ // context is given.
+ headless::HeadlessBrowserContext* browser_context = context_builder.Build();
+ browser->SetDefaultBrowserContext(browser_context);
+
+ // Get the URL from the command line.
+ base::CommandLine::StringVector args =
+ base::CommandLine::ForCurrentProcess()->GetArgs();
+ if (args.empty()) {
+ LOG(ERROR) << "No URL to load";
+ browser->Shutdown();
+ return;
+ }
+ GURL url(args[0]);
+
+ // Open a tab (i.e., HeadlessWebContents) in the newly created browser
+ // context.
+ headless::HeadlessWebContents::Builder tab_builder(
+ browser_context->CreateWebContentsBuilder());
+
+ // We can set options for the opened tab here. In this example we are just
+ // setting the initial URL to navigate to.
+ tab_builder.SetInitialURL(url);
+
+ // Create an instance of the example app, which will wait for the page to load
+ // and print its DOM.
+ headless::HeadlessWebContents* web_contents = tab_builder.Build();
+ g_example = new HeadlessExample(browser, web_contents);
+}
+
+int main(int argc, const char** argv) {
+ // This function must be the first thing we call to make sure child processes
+ // such as the renderer are started properly. The headless library starts
+ // child processes by forking and exec'ing the main application.
+ headless::RunChildProcessIfNeeded(argc, argv);
+
+ // Create a headless browser instance. There can be one of these per process
+ // and it can only be initialized once.
+ headless::HeadlessBrowser::Options::Builder builder(argc, argv);
+
+ // Here you can customize browser options. As an example we set the window
+ // size.
+ builder.SetWindowSize(gfx::Size(800, 600));
+
+ // Pass control to the headless library. It will bring up the browser and
+ // invoke the given callback on the browser UI thread. Note: if you need to
+ // pass more parameters to the callback, you can add them to the Bind() call
+ // below.
+ return headless::HeadlessBrowserMain(builder.Build(),
+ base::Bind(&OnHeadlessBrowserStarted));
+}
diff --git a/chromium/headless/app/headless_shell.cc b/chromium/headless/app/headless_shell.cc
index 047f408a0f4..7c187b00a82 100644
--- a/chromium/headless/app/headless_shell.cc
+++ b/chromium/headless/app/headless_shell.cc
@@ -7,30 +7,20 @@
#include <string>
#include "base/base64.h"
+#include "base/base_switches.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/json/json_writer.h"
#include "base/location.h"
-#include "base/memory/ptr_util.h"
-#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
-#include "content/public/common/content_switches.h"
+#include "headless/app/headless_shell.h"
#include "headless/app/headless_shell_switches.h"
-#include "headless/public/devtools/domains/emulation.h"
-#include "headless/public/devtools/domains/inspector.h"
-#include "headless/public/devtools/domains/page.h"
-#include "headless/public/devtools/domains/runtime.h"
-#include "headless/public/headless_browser.h"
-#include "headless/public/headless_devtools_client.h"
#include "headless/public/headless_devtools_target.h"
-#include "headless/public/headless_web_contents.h"
-#include "headless/public/util/deterministic_dispatcher.h"
#include "headless/public/util/deterministic_http_protocol_handler.h"
-#include "net/base/file_stream.h"
#include "net/base/io_buffer.h"
#include "net/base/ip_address.h"
#include "net/base/net_errors.h"
@@ -55,361 +45,358 @@ bool ParseWindowSize(std::string window_size, gfx::Size* parsed_window_size) {
}
} // namespace
-// An application which implements a simple headless browser.
-class HeadlessShell : public HeadlessWebContents::Observer,
- emulation::ExperimentalObserver,
- inspector::ExperimentalObserver,
- page::Observer {
- public:
- HeadlessShell()
- : browser_(nullptr),
- devtools_client_(HeadlessDevToolsClient::Create()),
- web_contents_(nullptr),
- processed_page_ready_(false),
- browser_context_(nullptr),
- weak_factory_(this) {}
- ~HeadlessShell() override {}
-
- void OnStart(HeadlessBrowser* browser) {
- browser_ = browser;
-
- HeadlessBrowserContext::Builder context_builder =
- browser_->CreateBrowserContextBuilder();
- // TODO(eseckler): These switches should also affect BrowserContexts that
- // are created via DevTools later.
- if (base::CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kDeterministicFetch)) {
- deterministic_dispatcher_.reset(
- new DeterministicDispatcher(browser_->BrowserIOThread()));
-
- ProtocolHandlerMap protocol_handlers;
- protocol_handlers[url::kHttpScheme] =
- base::MakeUnique<DeterministicHttpProtocolHandler>(
- deterministic_dispatcher_.get(), browser->BrowserIOThread());
- protocol_handlers[url::kHttpsScheme] =
- base::MakeUnique<DeterministicHttpProtocolHandler>(
- deterministic_dispatcher_.get(), browser->BrowserIOThread());
-
- context_builder.SetProtocolHandlers(std::move(protocol_handlers));
- }
- if (base::CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kHideScrollbars)) {
- context_builder.SetOverrideWebPreferencesCallback(
- base::Bind([](WebPreferences* preferences) {
- preferences->hide_scrollbars = true;
- }));
- }
- browser_context_ = context_builder.Build();
- browser_->SetDefaultBrowserContext(browser_context_);
-
- HeadlessWebContents::Builder builder(
- browser_context_->CreateWebContentsBuilder());
- base::CommandLine::StringVector args =
- base::CommandLine::ForCurrentProcess()->GetArgs();
-
- // TODO(alexclarke): Should we navigate to about:blank first if using
- // virtual time?
- if (args.empty())
- args.push_back("about:blank");
- for (auto it = args.rbegin(); it != args.rend(); ++it) {
- GURL url(*it);
- HeadlessWebContents* web_contents = builder.SetInitialURL(url).Build();
- if (!web_contents) {
- LOG(ERROR) << "Navigation to " << url << " failed";
- browser_->Shutdown();
- return;
- }
- if (!web_contents_ && !RemoteDebuggingEnabled()) {
- // TODO(jzfeng): Support observing multiple targets.
- url_ = url;
- web_contents_ = web_contents;
- web_contents_->AddObserver(this);
- }
- }
- }
-
- void Shutdown() {
- if (!web_contents_)
+HeadlessShell::HeadlessShell()
+ : browser_(nullptr),
+ devtools_client_(HeadlessDevToolsClient::Create()),
+ web_contents_(nullptr),
+ processed_page_ready_(false),
+ browser_context_(nullptr),
+ weak_factory_(this) {}
+
+HeadlessShell::~HeadlessShell() {}
+
+void HeadlessShell::OnStart(HeadlessBrowser* browser) {
+ browser_ = browser;
+
+ HeadlessBrowserContext::Builder context_builder =
+ browser_->CreateBrowserContextBuilder();
+ // TODO(eseckler): These switches should also affect BrowserContexts that
+ // are created via DevTools later.
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDeterministicFetch)) {
+ deterministic_dispatcher_.reset(
+ new DeterministicDispatcher(browser_->BrowserIOThread()));
+
+ ProtocolHandlerMap protocol_handlers;
+ protocol_handlers[url::kHttpScheme] =
+ base::MakeUnique<DeterministicHttpProtocolHandler>(
+ deterministic_dispatcher_.get(), browser->BrowserIOThread());
+ protocol_handlers[url::kHttpsScheme] =
+ base::MakeUnique<DeterministicHttpProtocolHandler>(
+ deterministic_dispatcher_.get(), browser->BrowserIOThread());
+
+ context_builder.SetProtocolHandlers(std::move(protocol_handlers));
+ }
+ browser_context_ = context_builder.Build();
+ browser_->SetDefaultBrowserContext(browser_context_);
+
+ HeadlessWebContents::Builder builder(
+ browser_context_->CreateWebContentsBuilder());
+ base::CommandLine::StringVector args =
+ base::CommandLine::ForCurrentProcess()->GetArgs();
+
+ // TODO(alexclarke): Should we navigate to about:blank first if using
+ // virtual time?
+ if (args.empty())
+#if defined(OS_WIN)
+ args.push_back(L"about:blank");
+#else
+ args.push_back("about:blank");
+#endif
+ for (auto it = args.rbegin(); it != args.rend(); ++it) {
+ GURL url(*it);
+ HeadlessWebContents* web_contents = builder.SetInitialURL(url).Build();
+ if (!web_contents) {
+ LOG(ERROR) << "Navigation to " << url << " failed";
+ browser_->Shutdown();
return;
- if (!RemoteDebuggingEnabled()) {
- devtools_client_->GetEmulation()->GetExperimental()->RemoveObserver(this);
- devtools_client_->GetInspector()->GetExperimental()->RemoveObserver(this);
- devtools_client_->GetPage()->RemoveObserver(this);
- if (web_contents_->GetDevToolsTarget()) {
- web_contents_->GetDevToolsTarget()->DetachClient(
- devtools_client_.get());
- }
- }
- web_contents_->RemoveObserver(this);
- web_contents_ = nullptr;
- browser_context_->Close();
- browser_->Shutdown();
- }
-
- // HeadlessWebContents::Observer implementation:
- void DevToolsTargetReady() override {
- web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get());
- devtools_client_->GetInspector()->GetExperimental()->AddObserver(this);
- devtools_client_->GetPage()->AddObserver(this);
- devtools_client_->GetPage()->Enable();
- // Check if the document had already finished loading by the time we
- // attached.
-
- devtools_client_->GetEmulation()->GetExperimental()->AddObserver(this);
-
- if (base::CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kVirtualTimeBudget)) {
- std::string budget_ms_ascii =
- base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
- switches::kVirtualTimeBudget);
- int budget_ms;
- CHECK(base::StringToInt(budget_ms_ascii, &budget_ms))
- << "Expected an integer value for --virtual-time-budget=";
- devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy(
- emulation::SetVirtualTimePolicyParams::Builder()
- .SetPolicy(emulation::VirtualTimePolicy::
- PAUSE_IF_NETWORK_FETCHES_PENDING)
- .SetBudget(budget_ms)
- .Build());
- } else {
- PollReadyState();
}
-
- if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTimeout)) {
- std::string timeout_ms_ascii =
- base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
- switches::kTimeout);
- int timeout_ms;
- CHECK(base::StringToInt(timeout_ms_ascii, &timeout_ms))
- << "Expected an integer value for --timeout=";
- browser_->BrowserMainThread()->PostDelayedTask(
- FROM_HERE,
- base::Bind(&HeadlessShell::FetchTimeout, weak_factory_.GetWeakPtr()),
- base::TimeDelta::FromMilliseconds(timeout_ms));
+ if (!web_contents_ && !RemoteDebuggingEnabled()) {
+ // TODO(jzfeng): Support observing multiple targets.
+ url_ = url;
+ web_contents_ = web_contents;
+ web_contents_->AddObserver(this);
}
-
- // TODO(skyostil): Implement more features to demonstrate the devtools API.
- }
-
- void FetchTimeout() {
- LOG(INFO) << "Timeout.";
- devtools_client_->GetPage()->GetExperimental()->StopLoading(
- page::StopLoadingParams::Builder().Build());
- }
-
- void OnTargetCrashed(const inspector::TargetCrashedParams& params) override {
- LOG(ERROR) << "Abnormal renderer termination.";
- // NB this never gets called if remote debugging is enabled.
- Shutdown();
}
+}
- void PollReadyState() {
- // We need to check the current location in addition to the ready state to
- // be sure the expected page is ready.
- devtools_client_->GetRuntime()->Evaluate(
- "document.readyState + ' ' + document.location.href",
- base::Bind(&HeadlessShell::OnReadyState, weak_factory_.GetWeakPtr()));
+void HeadlessShell::Shutdown() {
+ if (!web_contents_)
+ return;
+ if (!RemoteDebuggingEnabled()) {
+ devtools_client_->GetEmulation()->GetExperimental()->RemoveObserver(this);
+ devtools_client_->GetInspector()->GetExperimental()->RemoveObserver(this);
+ devtools_client_->GetPage()->GetExperimental()->RemoveObserver(this);
+ if (web_contents_->GetDevToolsTarget()) {
+ web_contents_->GetDevToolsTarget()->DetachClient(devtools_client_.get());
+ }
}
+ web_contents_->RemoveObserver(this);
+ web_contents_ = nullptr;
+ browser_context_->Close();
+ browser_->Shutdown();
+}
- void OnReadyState(std::unique_ptr<runtime::EvaluateResult> result) {
- std::string ready_state_and_url;
- if (result->GetResult()->GetValue()->GetAsString(&ready_state_and_url)) {
- std::stringstream stream(ready_state_and_url);
- std::string ready_state;
- std::string url;
- stream >> ready_state;
- stream >> url;
+void HeadlessShell::DevToolsTargetReady() {
+ web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get());
+ devtools_client_->GetInspector()->GetExperimental()->AddObserver(this);
+ devtools_client_->GetPage()->GetExperimental()->AddObserver(this);
+ devtools_client_->GetPage()->Enable();
+ // Check if the document had already finished loading by the time we
+ // attached.
+
+ devtools_client_->GetEmulation()->GetExperimental()->AddObserver(this);
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDeterministicFetch)) {
+ devtools_client_->GetPage()->GetExperimental()->SetControlNavigations(
+ headless::page::SetControlNavigationsParams::Builder()
+ .SetEnabled(true)
+ .Build());
+ }
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kVirtualTimeBudget)) {
+ std::string budget_ms_ascii =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kVirtualTimeBudget);
+ int budget_ms;
+ CHECK(base::StringToInt(budget_ms_ascii, &budget_ms))
+ << "Expected an integer value for --virtual-time-budget=";
+ devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy(
+ emulation::SetVirtualTimePolicyParams::Builder()
+ .SetPolicy(
+ emulation::VirtualTimePolicy::PAUSE_IF_NETWORK_FETCHES_PENDING)
+ .SetBudget(budget_ms)
+ .Build());
+ } else {
+ PollReadyState();
+ }
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTimeout)) {
+ std::string timeout_ms_ascii =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kTimeout);
+ int timeout_ms;
+ CHECK(base::StringToInt(timeout_ms_ascii, &timeout_ms))
+ << "Expected an integer value for --timeout=";
+ browser_->BrowserMainThread()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&HeadlessShell::FetchTimeout, weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(timeout_ms));
+ }
+
+ // TODO(skyostil): Implement more features to demonstrate the devtools API.
+}
- if (ready_state == "complete" &&
- (url_.spec() == url || url != "about:blank")) {
- OnPageReady();
- return;
- }
- }
- }
+void HeadlessShell::FetchTimeout() {
+ LOG(INFO) << "Timeout.";
+ devtools_client_->GetPage()->GetExperimental()->StopLoading(
+ page::StopLoadingParams::Builder().Build());
+}
- // emulation::Observer implementation:
- void OnVirtualTimeBudgetExpired(
- const emulation::VirtualTimeBudgetExpiredParams& params) override {
- OnPageReady();
- }
+void HeadlessShell::OnTargetCrashed(
+ const inspector::TargetCrashedParams& params) {
+ LOG(ERROR) << "Abnormal renderer termination.";
+ // NB this never gets called if remote debugging is enabled.
+ Shutdown();
+}
- // page::Observer implementation:
- void OnLoadEventFired(const page::LoadEventFiredParams& params) override {
- if (base::CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kVirtualTimeBudget)) {
- return;
- }
- OnPageReady();
- }
+void HeadlessShell::PollReadyState() {
+ // We need to check the current location in addition to the ready state to
+ // be sure the expected page is ready.
+ devtools_client_->GetRuntime()->Evaluate(
+ "document.readyState + ' ' + document.location.href",
+ base::Bind(&HeadlessShell::OnReadyState, weak_factory_.GetWeakPtr()));
+}
- void OnPageReady() {
- if (processed_page_ready_)
+void HeadlessShell::OnReadyState(
+ std::unique_ptr<runtime::EvaluateResult> result) {
+ std::string ready_state_and_url;
+ if (result->GetResult()->GetValue()->GetAsString(&ready_state_and_url)) {
+ std::stringstream stream(ready_state_and_url);
+ std::string ready_state;
+ std::string url;
+ stream >> ready_state;
+ stream >> url;
+
+ if (ready_state == "complete" &&
+ (url_.spec() == url || url != "about:blank")) {
+ OnPageReady();
return;
- processed_page_ready_ = true;
-
- if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kDumpDom)) {
- FetchDom();
- } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kRepl)) {
- LOG(INFO)
- << "Type a Javascript expression to evaluate or \"quit\" to exit.";
- InputExpression();
- } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kScreenshot)) {
- CaptureScreenshot();
- } else {
- Shutdown();
}
}
+}
- void FetchDom() {
- devtools_client_->GetRuntime()->Evaluate(
- "document.body.innerHTML",
- base::Bind(&HeadlessShell::OnDomFetched, weak_factory_.GetWeakPtr()));
- }
+// emulation::Observer implementation:
+void HeadlessShell::OnVirtualTimeBudgetExpired(
+ const emulation::VirtualTimeBudgetExpiredParams& params) {
+ OnPageReady();
+}
- void OnDomFetched(std::unique_ptr<runtime::EvaluateResult> result) {
- if (result->HasExceptionDetails()) {
- LOG(ERROR) << "Failed to evaluate document.body.innerHTML: "
- << result->GetExceptionDetails()->GetText();
- } else {
- std::string dom;
- if (result->GetResult()->GetValue()->GetAsString(&dom)) {
- printf("%s\n", dom.c_str());
- }
- }
- Shutdown();
+// page::Observer implementation:
+void HeadlessShell::OnLoadEventFired(const page::LoadEventFiredParams& params) {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kVirtualTimeBudget)) {
+ return;
}
+ OnPageReady();
+}
- void InputExpression() {
- // Note that a real system should read user input asynchronously, because
- // otherwise all other browser activity is suspended (e.g., page loading).
- printf(">>> ");
- std::stringstream expression;
- while (true) {
- int c = fgetc(stdin);
- if (c == EOF || c == '\n') {
- break;
- }
- expression << c;
- }
- if (expression.str() == "quit") {
- Shutdown();
- return;
- }
- devtools_client_->GetRuntime()->Evaluate(
- expression.str(), base::Bind(&HeadlessShell::OnExpressionResult,
- weak_factory_.GetWeakPtr()));
- }
+void HeadlessShell::OnNavigationRequested(
+ const headless::page::NavigationRequestedParams& params) {
+ deterministic_dispatcher_->NavigationRequested(
+ base::MakeUnique<ShellNavigationRequest>(weak_factory_.GetWeakPtr(),
+ params));
+}
- void OnExpressionResult(std::unique_ptr<runtime::EvaluateResult> result) {
- std::unique_ptr<base::Value> value = result->Serialize();
- std::string result_json;
- base::JSONWriter::Write(*value, &result_json);
- printf("%s\n", result_json.c_str());
+void HeadlessShell::OnPageReady() {
+ if (processed_page_ready_)
+ return;
+ processed_page_ready_ = true;
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kDumpDom)) {
+ FetchDom();
+ } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kRepl)) {
+ LOG(INFO)
+ << "Type a Javascript expression to evaluate or \"quit\" to exit.";
InputExpression();
+ } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kScreenshot)) {
+ CaptureScreenshot();
+ } else {
+ Shutdown();
}
+}
- void CaptureScreenshot() {
- devtools_client_->GetPage()->GetExperimental()->CaptureScreenshot(
- page::CaptureScreenshotParams::Builder().Build(),
- base::Bind(&HeadlessShell::OnScreenshotCaptured,
- weak_factory_.GetWeakPtr()));
- }
+void HeadlessShell::FetchDom() {
+ devtools_client_->GetRuntime()->Evaluate(
+ "document.body.outerHTML",
+ base::Bind(&HeadlessShell::OnDomFetched, weak_factory_.GetWeakPtr()));
+}
- void OnScreenshotCaptured(
- std::unique_ptr<page::CaptureScreenshotResult> result) {
- base::FilePath file_name =
- base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
- switches::kScreenshot);
- if (file_name.empty()) {
- file_name = base::FilePath().AppendASCII(kDefaultScreenshotFileName);
+void HeadlessShell::OnDomFetched(
+ std::unique_ptr<runtime::EvaluateResult> result) {
+ if (result->HasExceptionDetails()) {
+ LOG(ERROR) << "Failed to evaluate document.body.outerHTML: "
+ << result->GetExceptionDetails()->GetText();
+ } else {
+ std::string dom;
+ if (result->GetResult()->GetValue()->GetAsString(&dom)) {
+ printf("%s\n", dom.c_str());
}
+ }
+ Shutdown();
+}
- screenshot_file_stream_.reset(
- new net::FileStream(browser_->BrowserFileThread()));
- const int open_result = screenshot_file_stream_->Open(
- file_name, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE |
- base::File::FLAG_ASYNC,
- base::Bind(&HeadlessShell::OnScreenshotFileOpened,
- weak_factory_.GetWeakPtr(), base::Passed(std::move(result)),
- file_name));
- if (open_result != net::ERR_IO_PENDING) {
- // Operation could not be started.
- OnScreenshotFileOpened(nullptr, file_name, open_result);
+void HeadlessShell::InputExpression() {
+ // Note that a real system should read user input asynchronously, because
+ // otherwise all other browser activity is suspended (e.g., page loading).
+ printf(">>> ");
+ std::stringstream expression;
+ while (true) {
+ int c = fgetc(stdin);
+ if (c == EOF || c == '\n') {
+ break;
}
+ expression << static_cast<char>(c);
}
+ if (expression.str() == "quit") {
+ Shutdown();
+ return;
+ }
+ devtools_client_->GetRuntime()->Evaluate(
+ expression.str(), base::Bind(&HeadlessShell::OnExpressionResult,
+ weak_factory_.GetWeakPtr()));
+}
- void OnScreenshotFileOpened(
- std::unique_ptr<page::CaptureScreenshotResult> result,
- const base::FilePath file_name,
- const int open_result) {
- if (open_result != net::OK) {
- LOG(ERROR) << "Writing screenshot to file " << file_name.value()
- << " was unsuccessful, could not open file: "
- << net::ErrorToString(open_result);
- return;
- }
+void HeadlessShell::OnExpressionResult(
+ std::unique_ptr<runtime::EvaluateResult> result) {
+ std::unique_ptr<base::Value> value = result->Serialize();
+ std::string result_json;
+ base::JSONWriter::Write(*value, &result_json);
+ printf("%s\n", result_json.c_str());
+ InputExpression();
+}
- std::string decoded_png;
- base::Base64Decode(result->GetData(), &decoded_png);
- scoped_refptr<net::IOBufferWithSize> buf =
- new net::IOBufferWithSize(decoded_png.size());
- memcpy(buf->data(), decoded_png.data(), decoded_png.size());
- const int write_result = screenshot_file_stream_->Write(
- buf.get(), buf->size(),
- base::Bind(&HeadlessShell::OnScreenshotFileWritten,
- weak_factory_.GetWeakPtr(), file_name, buf->size()));
- if (write_result != net::ERR_IO_PENDING) {
- // Operation may have completed successfully or failed.
- OnScreenshotFileWritten(file_name, buf->size(), write_result);
- }
- }
+void HeadlessShell::CaptureScreenshot() {
+ devtools_client_->GetPage()->GetExperimental()->CaptureScreenshot(
+ page::CaptureScreenshotParams::Builder().Build(),
+ base::Bind(&HeadlessShell::OnScreenshotCaptured,
+ weak_factory_.GetWeakPtr()));
+}
- void OnScreenshotFileWritten(const base::FilePath file_name,
- const int length,
- const int write_result) {
- if (write_result < length) {
- // TODO(eseckler): Support recovering from partial writes.
- LOG(ERROR) << "Writing screenshot to file " << file_name.value()
- << " was unsuccessful: " << net::ErrorToString(write_result);
- } else {
- LOG(INFO) << "Screenshot written to file " << file_name.value() << "."
- << std::endl;
- }
- int close_result = screenshot_file_stream_->Close(base::Bind(
- &HeadlessShell::OnScreenshotFileClosed, weak_factory_.GetWeakPtr()));
- if (close_result != net::ERR_IO_PENDING) {
- // Operation could not be started.
- OnScreenshotFileClosed(close_result);
- }
+void HeadlessShell::OnScreenshotCaptured(
+ std::unique_ptr<page::CaptureScreenshotResult> result) {
+ base::FilePath file_name =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
+ switches::kScreenshot);
+ if (file_name.empty()) {
+ file_name = base::FilePath().AppendASCII(kDefaultScreenshotFileName);
+ }
+
+ screenshot_file_proxy_.reset(
+ new base::FileProxy(browser_->BrowserFileThread().get()));
+ if (!screenshot_file_proxy_->CreateOrOpen(
+ file_name, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE,
+ base::Bind(&HeadlessShell::OnScreenshotFileOpened,
+ weak_factory_.GetWeakPtr(),
+ base::Passed(std::move(result)), file_name))) {
+ // Operation could not be started.
+ OnScreenshotFileOpened(nullptr, file_name, base::File::FILE_ERROR_FAILED);
}
+}
- void OnScreenshotFileClosed(const int close_result) { Shutdown(); }
+void HeadlessShell::OnScreenshotFileOpened(
+ std::unique_ptr<page::CaptureScreenshotResult> result,
+ const base::FilePath file_name,
+ base::File::Error error_code) {
+ if (!screenshot_file_proxy_->IsValid()) {
+ LOG(ERROR) << "Writing screenshot to file " << file_name.value()
+ << " was unsuccessful, could not open file: "
+ << base::File::ErrorToString(error_code);
+ return;
+ }
+
+ std::string decoded_png;
+ base::Base64Decode(result->GetData(), &decoded_png);
+ scoped_refptr<net::IOBufferWithSize> buf =
+ new net::IOBufferWithSize(decoded_png.size());
+ memcpy(buf->data(), decoded_png.data(), decoded_png.size());
+
+ if (!screenshot_file_proxy_->Write(
+ 0, buf->data(), buf->size(),
+ base::Bind(&HeadlessShell::OnScreenshotFileWritten,
+ weak_factory_.GetWeakPtr(), file_name, buf->size()))) {
+ // Operation may have completed successfully or failed.
+ OnScreenshotFileWritten(file_name, buf->size(),
+ base::File::FILE_ERROR_FAILED, 0);
+ }
+}
- bool RemoteDebuggingEnabled() const {
- const base::CommandLine& command_line =
- *base::CommandLine::ForCurrentProcess();
- return command_line.HasSwitch(::switches::kRemoteDebuggingPort);
+void HeadlessShell::OnScreenshotFileWritten(const base::FilePath file_name,
+ const int length,
+ base::File::Error error_code,
+ int write_result) {
+ if (write_result < length) {
+ // TODO(eseckler): Support recovering from partial writes.
+ LOG(ERROR) << "Writing screenshot to file " << file_name.value()
+ << " was unsuccessful: " << net::ErrorToString(write_result);
+ } else {
+ LOG(INFO) << "Screenshot written to file " << file_name.value() << "."
+ << std::endl;
+ }
+ if (!screenshot_file_proxy_->Close(
+ base::Bind(&HeadlessShell::OnScreenshotFileClosed,
+ weak_factory_.GetWeakPtr()))) {
+ // Operation could not be started.
+ OnScreenshotFileClosed(base::File::FILE_ERROR_FAILED);
}
+}
- private:
- GURL url_;
- HeadlessBrowser* browser_; // Not owned.
- std::unique_ptr<HeadlessDevToolsClient> devtools_client_;
- HeadlessWebContents* web_contents_;
- bool processed_page_ready_;
- std::unique_ptr<net::FileStream> screenshot_file_stream_;
- HeadlessBrowserContext* browser_context_;
- std::unique_ptr<DeterministicDispatcher> deterministic_dispatcher_;
- base::WeakPtrFactory<HeadlessShell> weak_factory_;
+void HeadlessShell::OnScreenshotFileClosed(base::File::Error error_code) {
+ Shutdown();
+}
- DISALLOW_COPY_AND_ASSIGN(HeadlessShell);
-};
+bool HeadlessShell::RemoteDebuggingEnabled() const {
+ const base::CommandLine& command_line =
+ *base::CommandLine::ForCurrentProcess();
+ return command_line.HasSwitch(switches::kRemoteDebuggingPort);
+}
bool ValidateCommandLine(const base::CommandLine& command_line) {
- if (!command_line.HasSwitch(::switches::kRemoteDebuggingPort)) {
+ if (!command_line.HasSwitch(switches::kRemoteDebuggingPort)) {
if (command_line.GetArgs().size() <= 1)
return true;
LOG(ERROR) << "Open multiple tabs is only supported when the "
@@ -444,15 +431,24 @@ bool ValidateCommandLine(const base::CommandLine& command_line) {
}
int HeadlessShellMain(int argc, const char** argv) {
+ base::CommandLine::Init(argc, argv);
RunChildProcessIfNeeded(argc, argv);
HeadlessShell shell;
HeadlessBrowser::Options::Builder builder(argc, argv);
// Enable devtools if requested.
- base::CommandLine command_line(argc, argv);
+ const base::CommandLine& command_line(
+ *base::CommandLine::ForCurrentProcess());
if (!ValidateCommandLine(command_line))
return EXIT_FAILURE;
+ if (command_line.HasSwitch(::switches::kEnableCrashReporter))
+ builder.SetCrashReporterEnabled(true);
+ if (command_line.HasSwitch(switches::kCrashDumpsDir)) {
+ builder.SetCrashDumpsDir(
+ command_line.GetSwitchValuePath(switches::kCrashDumpsDir));
+ }
+
if (command_line.HasSwitch(::switches::kRemoteDebuggingPort)) {
std::string address = kDevToolsHttpServerAddress;
if (command_line.HasSwitch(switches::kRemoteDebuggingAddress)) {
@@ -491,9 +487,9 @@ int HeadlessShellMain(int argc, const char** argv) {
builder.SetProxyServer(parsed_proxy_server);
}
- if (command_line.HasSwitch(::switches::kHostResolverRules)) {
+ if (command_line.HasSwitch(switches::kHostResolverRules)) {
builder.SetHostResolverRules(
- command_line.GetSwitchValueASCII(::switches::kHostResolverRules));
+ command_line.GetSwitchValueASCII(switches::kHostResolverRules));
}
if (command_line.HasSwitch(switches::kUseGL)) {
@@ -518,6 +514,11 @@ int HeadlessShellMain(int argc, const char** argv) {
builder.SetWindowSize(parsed_window_size);
}
+ if (command_line.HasSwitch(switches::kHideScrollbars)) {
+ builder.SetOverrideWebPreferencesCallback(base::Bind([](
+ WebPreferences* preferences) { preferences->hide_scrollbars = true; }));
+ }
+
return HeadlessBrowserMain(
builder.Build(),
base::Bind(&HeadlessShell::OnStart, base::Unretained(&shell)));
diff --git a/chromium/headless/app/headless_shell.h b/chromium/headless/app/headless_shell.h
new file mode 100644
index 00000000000..4b0e83f6cbf
--- /dev/null
+++ b/chromium/headless/app/headless_shell.h
@@ -0,0 +1,98 @@
+// Copyright 2017 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/files/file_proxy.h"
+#include "base/memory/weak_ptr.h"
+#include "headless/app/shell_navigation_request.h"
+#include "headless/public/devtools/domains/emulation.h"
+#include "headless/public/devtools/domains/inspector.h"
+#include "headless/public/devtools/domains/page.h"
+#include "headless/public/devtools/domains/runtime.h"
+#include "headless/public/headless_browser.h"
+#include "headless/public/headless_devtools_client.h"
+#include "headless/public/headless_web_contents.h"
+#include "headless/public/util/deterministic_dispatcher.h"
+#include "net/base/file_stream.h"
+
+namespace headless {
+
+// An application which implements a simple headless browser.
+class HeadlessShell : public HeadlessWebContents::Observer,
+ public emulation::ExperimentalObserver,
+ public inspector::ExperimentalObserver,
+ public page::ExperimentalObserver {
+ public:
+ HeadlessShell();
+ ~HeadlessShell() override;
+
+ // HeadlessWebContents::Observer implementation:
+ void DevToolsTargetReady() override;
+ void OnTargetCrashed(const inspector::TargetCrashedParams& params) override;
+
+ // emulation::Observer implementation:
+ void OnVirtualTimeBudgetExpired(
+ const emulation::VirtualTimeBudgetExpiredParams& params) override;
+
+ // page::Observer implementation:
+ void OnLoadEventFired(const page::LoadEventFiredParams& params) override;
+ void OnNavigationRequested(
+ const headless::page::NavigationRequestedParams& params) override;
+
+ void OnStart(HeadlessBrowser* browser);
+ void Shutdown();
+
+ void FetchTimeout();
+
+ void PollReadyState();
+
+ void OnReadyState(std::unique_ptr<runtime::EvaluateResult> result);
+
+ void OnPageReady();
+
+ void FetchDom();
+
+ void OnDomFetched(std::unique_ptr<runtime::EvaluateResult> result);
+
+ void InputExpression();
+
+ void OnExpressionResult(std::unique_ptr<runtime::EvaluateResult> result);
+
+ void CaptureScreenshot();
+
+ void OnScreenshotCaptured(
+ std::unique_ptr<page::CaptureScreenshotResult> result);
+
+ void OnScreenshotFileOpened(
+ std::unique_ptr<page::CaptureScreenshotResult> result,
+ const base::FilePath file_name,
+ base::File::Error error_code);
+
+ void OnScreenshotFileWritten(const base::FilePath file_name,
+ const int length,
+ base::File::Error error_code,
+ int write_result);
+
+ void OnScreenshotFileClosed(base::File::Error error_code);
+
+ bool RemoteDebuggingEnabled() const;
+
+ HeadlessDevToolsClient* devtools_client() const {
+ return devtools_client_.get();
+ }
+
+ private:
+ GURL url_;
+ HeadlessBrowser* browser_; // Not owned.
+ std::unique_ptr<HeadlessDevToolsClient> devtools_client_;
+ HeadlessWebContents* web_contents_;
+ bool processed_page_ready_;
+ std::unique_ptr<base::FileProxy> screenshot_file_proxy_;
+ HeadlessBrowserContext* browser_context_;
+ std::unique_ptr<DeterministicDispatcher> deterministic_dispatcher_;
+ base::WeakPtrFactory<HeadlessShell> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(HeadlessShell);
+};
+
+} // namespace headless
diff --git a/chromium/headless/app/headless_shell_switches.cc b/chromium/headless/app/headless_shell_switches.cc
index 06750596296..f20a60657f7 100644
--- a/chromium/headless/app/headless_shell_switches.cc
+++ b/chromium/headless/app/headless_shell_switches.cc
@@ -7,6 +7,9 @@
namespace headless {
namespace switches {
+// The directory breakpad should store minidumps in.
+const char kCrashDumpsDir[] = "crash-dumps-dir";
+
// Instructs headless_shell to cause network fetches to complete in order of
// creation. This removes a significant source of network related
// non-determinism at the cost of slower page loads.
diff --git a/chromium/headless/app/headless_shell_switches.h b/chromium/headless/app/headless_shell_switches.h
index 9d0a348e6e7..929fda626ac 100644
--- a/chromium/headless/app/headless_shell_switches.h
+++ b/chromium/headless/app/headless_shell_switches.h
@@ -5,8 +5,11 @@
#ifndef HEADLESS_APP_HEADLESS_SHELL_SWITCHES_H_
#define HEADLESS_APP_HEADLESS_SHELL_SWITCHES_H_
+#include "content/public/common/content_switches.h"
+
namespace headless {
namespace switches {
+extern const char kCrashDumpsDir[];
extern const char kDeterministicFetch[];
extern const char kDumpDom[];
extern const char kHideScrollbars[];
@@ -19,6 +22,11 @@ extern const char kUseGL[];
extern const char kUserDataDir[];
extern const char kVirtualTimeBudget[];
extern const char kWindowSize[];
+
+// Switches which are replicated from content.
+using ::switches::kHostResolverRules;
+using ::switches::kRemoteDebuggingPort;
+
} // namespace switches
} // namespace headless
diff --git a/chromium/headless/app/shell_navigation_request.cc b/chromium/headless/app/shell_navigation_request.cc
new file mode 100644
index 00000000000..3c419bce8ec
--- /dev/null
+++ b/chromium/headless/app/shell_navigation_request.cc
@@ -0,0 +1,43 @@
+// Copyright 2017 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/app/shell_navigation_request.h"
+
+#include "headless/app/headless_shell.h"
+
+namespace headless {
+
+ShellNavigationRequest::ShellNavigationRequest(
+ base::WeakPtr<HeadlessShell> headless_shell,
+ const page::NavigationRequestedParams& params)
+ : headless_shell_(headless_shell),
+ navigation_id_(params.GetNavigationId()) {}
+
+ShellNavigationRequest::~ShellNavigationRequest() {}
+
+void ShellNavigationRequest::StartProcessing(base::Closure done_callback) {
+ if (!headless_shell_)
+ return;
+
+ // Allow the navigation to proceed.
+ headless_shell_->devtools_client()
+ ->GetPage()
+ ->GetExperimental()
+ ->ProcessNavigation(
+ headless::page::ProcessNavigationParams::Builder()
+ .SetNavigationId(navigation_id_)
+ .SetResponse(headless::page::NavigationResponse::PROCEED)
+ .Build(),
+ base::Bind(&ShellNavigationRequest::ProcessNavigationResult,
+ done_callback));
+}
+
+// static
+void ShellNavigationRequest::ProcessNavigationResult(
+ base::Closure done_callback,
+ std::unique_ptr<page::ProcessNavigationResult>) {
+ done_callback.Run();
+}
+
+} // namespace headless
diff --git a/chromium/headless/app/shell_navigation_request.h b/chromium/headless/app/shell_navigation_request.h
new file mode 100644
index 00000000000..20ef77f0e4e
--- /dev/null
+++ b/chromium/headless/app/shell_navigation_request.h
@@ -0,0 +1,41 @@
+// Copyright 2017 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_UTIL_HTTP_URL_FETCHER_H_
+#define HEADLESS_PUBLIC_UTIL_HTTP_URL_FETCHER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "headless/public/devtools/domains/page.h"
+#include "headless/public/util/navigation_request.h"
+
+namespace headless {
+class HeadlessShell;
+
+// Used in deterministic mode to make sure navigations and resource requests
+// complete in the order requested.
+class ShellNavigationRequest : public NavigationRequest {
+ public:
+ ShellNavigationRequest(base::WeakPtr<HeadlessShell> headless_shell,
+ const page::NavigationRequestedParams& params);
+
+ ~ShellNavigationRequest() override;
+
+ void StartProcessing(base::Closure done_callback) override;
+
+ private:
+ // Note the navigation likely isn't done when this is called, however we
+ // expect it will have been committed and the initial resource load requested.
+ static void ProcessNavigationResult(
+ base::Closure done_callback,
+ std::unique_ptr<page::ProcessNavigationResult>);
+
+ base::WeakPtr<HeadlessShell> headless_shell_;
+ int navigation_id_;
+};
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_HTTP_URL_FETCHER_H_
diff --git a/chromium/headless/headless.gni b/chromium/headless/headless.gni
new file mode 100644
index 00000000000..0cc7722927c
--- /dev/null
+++ b/chromium/headless/headless.gni
@@ -0,0 +1,8 @@
+# Copyright 2017 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.
+
+declare_args() {
+ # Embed resource.pak file into the binary for easier distribution.
+ headless_use_embedded_resources = false
+}
diff --git a/chromium/headless/lib/browser/DEPS b/chromium/headless/lib/browser/DEPS
index 1704e22e758..0df32e1eda5 100644
--- a/chromium/headless/lib/browser/DEPS
+++ b/chromium/headless/lib/browser/DEPS
@@ -1,4 +1,5 @@
include_rules = [
"+components/security_state",
+ "+storage/browser/quota",
"+ui/aura",
]
diff --git a/chromium/headless/lib/browser/devtools_api/client_api_generator.py b/chromium/headless/lib/browser/devtools_api/client_api_generator.py
index 435358af73d..fd011e10ffd 100644
--- a/chromium/headless/lib/browser/devtools_api/client_api_generator.py
+++ b/chromium/headless/lib/browser/devtools_api/client_api_generator.py
@@ -65,6 +65,11 @@ def SanitizeLiteral(literal):
return {
# Rename null enumeration values to avoid a clash with the NULL macro.
'null': 'none',
+ # Rename literals that clash with Win32 defined macros.
+ 'error': 'err',
+ 'mouseMoved': 'mouse_ptr_moved',
+ 'Strict': 'exact',
+ 'getCurrentTime': 'getCurrentAnimationTime',
# Rename mathematical constants to avoid colliding with C macros.
'Infinity': 'InfinityValue',
'-Infinity': 'NegativeInfinityValue',
@@ -321,10 +326,10 @@ def SynthesizeCommandTypes(json_api):
if 'enum' in parameter and not '$ref' in parameter:
SynthesizeEnumType(domain, command['name'], parameter)
parameters_type = {
- 'id': ToTitleCase(command['name']) + 'Params',
+ 'id': ToTitleCase(SanitizeLiteral(command['name'])) + 'Params',
'type': 'object',
'description': 'Parameters for the %s command.' % ToTitleCase(
- command['name']),
+ SanitizeLiteral(command['name'])),
'properties': command['parameters']
}
domain['types'].append(parameters_type)
@@ -333,10 +338,10 @@ def SynthesizeCommandTypes(json_api):
if 'enum' in parameter and not '$ref' in parameter:
SynthesizeEnumType(domain, command['name'], parameter)
result_type = {
- 'id': ToTitleCase(command['name']) + 'Result',
+ 'id': ToTitleCase(SanitizeLiteral(command['name'])) + 'Result',
'type': 'object',
'description': 'Result for the %s command.' % ToTitleCase(
- command['name']),
+ SanitizeLiteral(command['name'])),
'properties': command['returns']
}
domain['types'].append(result_type)
diff --git a/chromium/headless/lib/browser/devtools_api/domain_cc.template b/chromium/headless/lib/browser/devtools_api/domain_cc.template
index dc04d6e9d7b..f9b70377917 100644
--- a/chromium/headless/lib/browser/devtools_api/domain_cc.template
+++ b/chromium/headless/lib/browser/devtools_api/domain_cc.template
@@ -45,7 +45,7 @@ void Domain::RegisterEventHandlersIfNeeded() {
{% if "redirect" in command %}{% continue %}{% endif %}
{% set class_name = 'ExperimentalDomain' if command.experimental else 'Domain' %}
- {% set method_name = command.name | to_title_case %}
+ {% set method_name = command.name | sanitize_literal | to_title_case %}
void {{class_name}}::{{method_name}}(std::unique_ptr<{{method_name}}Params> params, base::Callback<void(std::unique_ptr<{{method_name}}Result>)> callback) {
dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", params->Serialize(), base::Bind(&Domain::Handle{{method_name}}Response, callback));
}
@@ -96,7 +96,7 @@ void {{class_name}}::{{method_name}}(std::unique_ptr<{{method_name}}Params> para
{# Generate response handlers for commands that need them. #}
{% for command in domain.commands %}
{% if not "returns" in command %}{% continue %}{% endif %}
- {% set method_name = command.name | to_title_case %}
+ {% set method_name = command.name | sanitize_literal | to_title_case %}
// static
void Domain::Handle{{method_name}}Response(base::Callback<void(std::unique_ptr<{{method_name}}Result>)> callback, const base::Value& response) {
diff --git a/chromium/headless/lib/browser/devtools_api/domain_h.template b/chromium/headless/lib/browser/devtools_api/domain_h.template
index 73110412f43..50073c08a35 100644
--- a/chromium/headless/lib/browser/devtools_api/domain_h.template
+++ b/chromium/headless/lib/browser/devtools_api/domain_h.template
@@ -18,7 +18,7 @@
{# Macro for defining a member function for a given command. #}
{% macro command_decl(command) %}
- {% set method_name = command.name | to_title_case %}
+ {% set method_name = command.name | sanitize_literal | to_title_case %}
{% if command.description %}
// {{ command.description }}
{% endif %}
@@ -108,7 +108,7 @@ class HEADLESS_EXPORT Domain {
{# Generate response handlers for commands that need them. #}
{% for command in domain.commands %}
{% if not "returns" in command %}{% continue %}{% endif %}
- {% set method_name = command.name | to_title_case %}
+ {% set method_name = command.name | sanitize_literal | to_title_case %}
static void Handle{{method_name}}Response(base::Callback<void(std::unique_ptr<{{method_name}}Result>)> callback, const base::Value& response);
{% endfor %}
diff --git a/chromium/headless/lib/browser/headless_browser_context_impl.cc b/chromium/headless/lib/browser/headless_browser_context_impl.cc
index df65523f34b..29c9da3f747 100644
--- a/chromium/headless/lib/browser/headless_browser_context_impl.cc
+++ b/chromium/headless/lib/browser/headless_browser_context_impl.cc
@@ -21,7 +21,6 @@
#include "headless/public/util/black_hole_protocol_handler.h"
#include "headless/public/util/in_memory_protocol_handler.h"
#include "net/url_request/url_request_context.h"
-#include "ui/aura/window_tree_host.h"
namespace headless {
@@ -249,8 +248,7 @@ HeadlessWebContents* HeadlessBrowserContextImpl::CreateWebContents(
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::unique_ptr<HeadlessWebContentsImpl> headless_web_contents =
- HeadlessWebContentsImpl::Create(builder,
- browser()->window_tree_host()->window());
+ HeadlessWebContentsImpl::Create(builder);
if (!headless_web_contents) {
return nullptr;
@@ -322,6 +320,13 @@ HeadlessBrowserContext::Builder& HeadlessBrowserContext::Builder::SetUserAgent(
}
HeadlessBrowserContext::Builder&
+HeadlessBrowserContext::Builder::SetProductNameAndVersion(
+ const std::string& product_name_and_version) {
+ options_->product_name_and_version_ = product_name_and_version;
+ return *this;
+}
+
+HeadlessBrowserContext::Builder&
HeadlessBrowserContext::Builder::SetProxyServer(
const net::HostPortPair& proxy_server) {
options_->proxy_server_ = proxy_server;
diff --git a/chromium/headless/lib/browser/headless_browser_context_options.cc b/chromium/headless/lib/browser/headless_browser_context_options.cc
index 13faa83889e..f9fa8248e5b 100644
--- a/chromium/headless/lib/browser/headless_browser_context_options.cc
+++ b/chromium/headless/lib/browser/headless_browser_context_options.cc
@@ -35,6 +35,12 @@ HeadlessBrowserContextOptions::HeadlessBrowserContextOptions(
HeadlessBrowser::Options* options)
: browser_options_(options) {}
+const std::string& HeadlessBrowserContextOptions::product_name_and_version()
+ const {
+ return ReturnOverriddenValue(product_name_and_version_,
+ browser_options_->product_name_and_version);
+}
+
const std::string& HeadlessBrowserContextOptions::user_agent() const {
return ReturnOverriddenValue(user_agent_, browser_options_->user_agent);
}
@@ -61,6 +67,13 @@ bool HeadlessBrowserContextOptions::incognito_mode() const {
browser_options_->incognito_mode);
}
+const base::Callback<void(WebPreferences*)>&
+HeadlessBrowserContextOptions::override_web_preferences_callback() const {
+ return ReturnOverriddenValue(
+ override_web_preferences_callback_,
+ browser_options_->override_web_preferences_callback);
+}
+
const ProtocolHandlerMap& HeadlessBrowserContextOptions::protocol_handlers()
const {
return protocol_handlers_;
@@ -70,9 +83,4 @@ ProtocolHandlerMap HeadlessBrowserContextOptions::TakeProtocolHandlers() {
return std::move(protocol_handlers_);
}
-const base::Callback<void(WebPreferences*)>&
-HeadlessBrowserContextOptions::override_web_preferences_callback() const {
- return override_web_preferences_callback_;
-}
-
} // namespace headless
diff --git a/chromium/headless/lib/browser/headless_browser_context_options.h b/chromium/headless/lib/browser/headless_browser_context_options.h
index 4c6601757f4..61f2b23d83c 100644
--- a/chromium/headless/lib/browser/headless_browser_context_options.h
+++ b/chromium/headless/lib/browser/headless_browser_context_options.h
@@ -26,6 +26,7 @@ class HeadlessBrowserContextOptions {
HeadlessBrowserContextOptions& operator=(
HeadlessBrowserContextOptions&& options);
+ const std::string& product_name_and_version() const;
const std::string& user_agent() const;
// See HeadlessBrowser::Options::proxy_server.
@@ -60,15 +61,17 @@ class HeadlessBrowserContextOptions {
HeadlessBrowser::Options* browser_options_;
+ base::Optional<std::string> product_name_and_version_;
base::Optional<std::string> user_agent_;
base::Optional<net::HostPortPair> proxy_server_;
base::Optional<std::string> host_resolver_rules_;
base::Optional<gfx::Size> window_size_;
base::Optional<base::FilePath> user_data_dir_;
base::Optional<bool> incognito_mode_;
+ base::Optional<base::Callback<void(WebPreferences*)>>
+ override_web_preferences_callback_;
ProtocolHandlerMap protocol_handlers_;
- base::Callback<void(WebPreferences*)> override_web_preferences_callback_;
DISALLOW_COPY_AND_ASSIGN(HeadlessBrowserContextOptions);
};
diff --git a/chromium/headless/lib/browser/headless_browser_impl.cc b/chromium/headless/lib/browser/headless_browser_impl.cc
index 93d6918a76c..71b79a09190 100644
--- a/chromium/headless/lib/browser/headless_browser_impl.cc
+++ b/chromium/headless/lib/browser/headless_browser_impl.cc
@@ -18,9 +18,8 @@
#include "headless/lib/browser/headless_browser_context_impl.h"
#include "headless/lib/browser/headless_browser_main_parts.h"
#include "headless/lib/browser/headless_web_contents_impl.h"
-#include "headless/lib/browser/headless_window_parenting_client.h"
-#include "headless/lib/browser/headless_window_tree_host.h"
#include "headless/lib/headless_content_main_delegate.h"
+#include "ui/aura/client/focus_client.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/events/devices/device_data_manager.h"
@@ -121,17 +120,7 @@ void HeadlessBrowserImpl::set_browser_main_parts(
}
void HeadlessBrowserImpl::RunOnStartCallback() {
- DCHECK(aura::Env::GetInstance());
- ui::DeviceDataManager::CreateInstance();
-
- window_tree_host_.reset(
- new HeadlessWindowTreeHost(gfx::Rect(options()->window_size)));
- window_tree_host_->InitHost();
- window_tree_host_->window()->Show();
-
- window_parenting_client_.reset(
- new HeadlessWindowParentingClient(window_tree_host_->window()));
-
+ PlatformCreateWindow();
on_start_callback_.Run(this);
on_start_callback_ = base::Callback<void(HeadlessBrowser*)>();
}
@@ -179,10 +168,6 @@ base::WeakPtr<HeadlessBrowserImpl> HeadlessBrowserImpl::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
-aura::WindowTreeHost* HeadlessBrowserImpl::window_tree_host() const {
- return window_tree_host_.get();
-}
-
HeadlessWebContents* HeadlessBrowserImpl::GetWebContentsForDevToolsAgentHostId(
const std::string& devtools_agent_host_id) {
for (HeadlessBrowserContext* context : GetAllBrowserContexts()) {
@@ -203,8 +188,9 @@ HeadlessBrowserContext* HeadlessBrowserImpl::GetBrowserContextForId(
}
void RunChildProcessIfNeeded(int argc, const char** argv) {
- base::CommandLine command_line(argc, argv);
- if (!command_line.HasSwitch(switches::kProcessType))
+ base::CommandLine::Init(argc, argv);
+ if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kProcessType))
return;
HeadlessBrowser::Options::Builder builder(argc, argv);
@@ -223,8 +209,8 @@ int HeadlessBrowserMain(
browser_was_initialized = true;
// Child processes should not end up here.
- base::CommandLine command_line(options.argc, options.argv);
- DCHECK(!command_line.HasSwitch(switches::kProcessType));
+ DCHECK(!base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kProcessType));
#endif
return RunContentMain(std::move(options),
std::move(on_browser_start_callback));
diff --git a/chromium/headless/lib/browser/headless_browser_impl.h b/chromium/headless/lib/browser/headless_browser_impl.h
index f37dff1dbc4..864e49c1c17 100644
--- a/chromium/headless/lib/browser/headless_browser_impl.h
+++ b/chromium/headless/lib/browser/headless_browser_impl.h
@@ -13,16 +13,19 @@
#include <vector>
#include "base/memory/weak_ptr.h"
+#include "content/public/browser/web_contents.h"
#include "headless/lib/browser/headless_devtools_manager_delegate.h"
#include "headless/lib/browser/headless_web_contents_impl.h"
-namespace aura {
-class WindowTreeHost;
+#if defined(USE_AURA)
+#include "headless/lib/browser/headless_window_tree_host.h"
+namespace aura {
namespace client {
-class WindowParentingClient;
+class FocusClient;
}
}
+#endif
namespace headless {
@@ -70,18 +73,26 @@ class HeadlessBrowserImpl : public HeadlessBrowser {
base::WeakPtr<HeadlessBrowserImpl> GetWeakPtr();
- aura::WindowTreeHost* window_tree_host() const;
+ // All the methods that begin with Platform need to be implemented by the
+ // platform specific headless implementation.
+ // Helper for one time initialization of application
+ void PlatformInitialize();
+ void PlatformCreateWindow();
+ void PlatformInitializeWebContents(const gfx::Size& initial_size,
+ content::WebContents* web_contents);
protected:
- base::Callback<void(HeadlessBrowser*)> on_start_callback_;
- HeadlessBrowser::Options options_;
- HeadlessBrowserMainParts* browser_main_parts_; // Not owned.
-
+#if defined(USE_AURA)
// TODO(eseckler): Currently one window and one window_tree_host
// is used for all web contents. We should probably use one
// window per web contents, but additional investigation is needed.
- std::unique_ptr<aura::WindowTreeHost> window_tree_host_;
- std::unique_ptr<aura::client::WindowParentingClient> window_parenting_client_;
+ std::unique_ptr<HeadlessWindowTreeHost> window_tree_host_;
+ std::unique_ptr<aura::client::FocusClient> focus_client_;
+#endif
+
+ base::Callback<void(HeadlessBrowser*)> on_start_callback_;
+ HeadlessBrowser::Options options_;
+ HeadlessBrowserMainParts* browser_main_parts_; // Not owned.
std::unordered_map<std::string, std::unique_ptr<HeadlessBrowserContextImpl>>
browser_contexts_;
diff --git a/chromium/headless/lib/browser/headless_browser_impl_aura.cc b/chromium/headless/lib/browser/headless_browser_impl_aura.cc
new file mode 100644
index 00000000000..4718d4914dc
--- /dev/null
+++ b/chromium/headless/lib/browser/headless_browser_impl_aura.cc
@@ -0,0 +1,56 @@
+// Copyright 2017 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 "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
+#include "headless/lib/browser/headless_focus_client.h"
+#include "headless/lib/browser/headless_screen.h"
+#include "ui/aura/client/focus_client.h"
+#include "ui/aura/env.h"
+#include "ui/aura/window.h"
+#include "ui/display/screen.h"
+#include "ui/events/devices/device_data_manager.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace headless {
+
+void HeadlessBrowserImpl::PlatformInitialize() {
+ HeadlessScreen* screen = HeadlessScreen::Create(options()->window_size);
+ display::Screen::SetScreenInstance(screen);
+}
+
+void HeadlessBrowserImpl::PlatformCreateWindow() {
+ DCHECK(aura::Env::GetInstance());
+ ui::DeviceDataManager::CreateInstance();
+
+ window_tree_host_.reset(
+ new HeadlessWindowTreeHost(gfx::Rect(options()->window_size)));
+ window_tree_host_->InitHost();
+ window_tree_host_->window()->Show();
+ window_tree_host_->SetParentWindow(window_tree_host_->window());
+
+ focus_client_.reset(new HeadlessFocusClient());
+ aura::client::SetFocusClient(window_tree_host_->window(),
+ focus_client_.get());
+}
+
+void HeadlessBrowserImpl::PlatformInitializeWebContents(
+ const gfx::Size& initial_size,
+ content::WebContents* web_contents) {
+ gfx::NativeView contents = web_contents->GetNativeView();
+ gfx::NativeWindow parent_window = window_tree_host_->window();
+ 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);
+}
+
+} // namespace headless
diff --git a/chromium/headless/lib/browser/headless_browser_impl_mac.mm b/chromium/headless/lib/browser/headless_browser_impl_mac.mm
new file mode 100644
index 00000000000..e1911162d6e
--- /dev/null
+++ b/chromium/headless/lib/browser/headless_browser_impl_mac.mm
@@ -0,0 +1,27 @@
+// Copyright 2017 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 "content/public/browser/web_contents.h"
+#include "ui/base/cocoa/base_view.h"
+
+namespace headless {
+
+void HeadlessBrowserImpl::PlatformInitialize() {}
+
+void HeadlessBrowserImpl::PlatformCreateWindow() {}
+
+void HeadlessBrowserImpl::PlatformInitializeWebContents(
+ const gfx::Size& initial_size,
+ content::WebContents* web_contents) {
+ NSView* web_view = web_contents->GetNativeView();
+ [web_view setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
+
+ NSRect frame = NSMakeRect(0, 0, initial_size.width(), initial_size.height());
+ [web_view setFrame:frame];
+ return;
+}
+
+} // namespace headless
diff --git a/chromium/headless/lib/browser/headless_browser_main_parts.cc b/chromium/headless/lib/browser/headless_browser_main_parts.cc
index b219480b6f7..2ae52c3cc31 100644
--- a/chromium/headless/lib/browser/headless_browser_main_parts.cc
+++ b/chromium/headless/lib/browser/headless_browser_main_parts.cc
@@ -8,23 +8,9 @@
#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/display/screen.h"
namespace headless {
-namespace {
-
-void PlatformInitialize(const gfx::Size& screen_size) {
- HeadlessScreen* screen = HeadlessScreen::Create(screen_size);
- display::Screen::SetScreenInstance(screen);
-}
-
-void PlatformExit() {
-}
-
-} // namespace
-
HeadlessBrowserMainParts::HeadlessBrowserMainParts(HeadlessBrowserImpl* browser)
: browser_(browser)
, devtools_http_handler_started_(false) {}
@@ -36,7 +22,7 @@ void HeadlessBrowserMainParts::PreMainMessageLoopRun() {
StartLocalDevToolsHttpHandler(browser_->options());
devtools_http_handler_started_ = true;
}
- PlatformInitialize(browser_->options()->window_size);
+ browser_->PlatformInitialize();
}
void HeadlessBrowserMainParts::PostMainMessageLoopRun() {
@@ -44,7 +30,6 @@ void HeadlessBrowserMainParts::PostMainMessageLoopRun() {
StopLocalDevToolsHttpHandler();
devtools_http_handler_started_ = false;
}
- PlatformExit();
}
} // namespace headless
diff --git a/chromium/headless/lib/browser/headless_content_browser_client.cc b/chromium/headless/lib/browser/headless_content_browser_client.cc
index 8ba656c082c..5876199424b 100644
--- a/chromium/headless/lib/browser/headless_content_browser_client.cc
+++ b/chromium/headless/lib/browser/headless_content_browser_client.cc
@@ -7,26 +7,100 @@
#include <memory>
#include <unordered_set>
+#include "base/base_switches.h"
#include "base/callback.h"
+#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/memory/ptr_util.h"
+#include "base/path_service.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/common/content_switches.h"
#include "content/public/common/service_names.mojom.h"
#include "headless/grit/headless_lib_resources.h"
#include "headless/lib/browser/headless_browser_context_impl.h"
#include "headless/lib/browser/headless_browser_impl.h"
#include "headless/lib/browser/headless_browser_main_parts.h"
#include "headless/lib/browser/headless_devtools_manager_delegate.h"
+#include "headless/lib/headless_macros.h"
+#include "storage/browser/quota/quota_settings.h"
#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/switches.h"
+
+#if defined(HEADLESS_USE_BREAKPAD)
+#include "base/debug/leak_annotations.h"
+#include "components/crash/content/app/breakpad_linux.h"
+#include "components/crash/content/browser/crash_handler_host_linux.h"
+#include "content/public/common/content_descriptors.h"
+#endif // defined(HEADLESS_USE_BREAKPAD)
namespace headless {
namespace {
const char kCapabilityPath[] =
"interface_provider_specs.navigation:frame.provides.renderer";
+
+#if defined(HEADLESS_USE_BREAKPAD)
+breakpad::CrashHandlerHostLinux* CreateCrashHandlerHost(
+ const std::string& process_type,
+ const HeadlessBrowser::Options& options) {
+ base::FilePath dumps_path = options.crash_dumps_dir;
+ if (dumps_path.empty()) {
+ bool ok = PathService::Get(base::DIR_MODULE, &dumps_path);
+ DCHECK(ok);
+ }
+
+ {
+ ANNOTATE_SCOPED_MEMORY_LEAK;
+#if defined(OFFICIAL_BUILD)
+ // Upload crash dumps in official builds, unless we're running in unattended
+ // mode (not to be confused with headless mode in general -- see
+ // chrome/common/env_vars.cc).
+ static const char kHeadless[] = "CHROME_HEADLESS";
+ bool upload = (getenv(kHeadless) == nullptr);
+#else
+ bool upload = false;
+#endif
+ breakpad::CrashHandlerHostLinux* crash_handler =
+ new breakpad::CrashHandlerHostLinux(process_type, dumps_path, upload);
+ crash_handler->StartUploaderThread();
+ return crash_handler;
+ }
+}
+
+int GetCrashSignalFD(const base::CommandLine& command_line,
+ const HeadlessBrowser::Options& options) {
+ if (!breakpad::IsCrashReporterEnabled())
+ return -1;
+
+ std::string process_type =
+ command_line.GetSwitchValueASCII(switches::kProcessType);
+
+ if (process_type == switches::kRendererProcess) {
+ static breakpad::CrashHandlerHostLinux* crash_handler =
+ CreateCrashHandlerHost(process_type, options);
+ return crash_handler->GetDeathSignalSocket();
+ }
+
+ if (process_type == switches::kPpapiPluginProcess) {
+ static breakpad::CrashHandlerHostLinux* crash_handler =
+ CreateCrashHandlerHost(process_type, options);
+ return crash_handler->GetDeathSignalSocket();
+ }
+
+ if (process_type == switches::kGpuProcess) {
+ static breakpad::CrashHandlerHostLinux* crash_handler =
+ CreateCrashHandlerHost(process_type, options);
+ return crash_handler->GetDeathSignalSocket();
+ }
+
+ return -1;
+}
+#endif // defined(HEADLESS_USE_BREAKPAD)
+
} // namespace
HeadlessContentBrowserClient::HeadlessContentBrowserClient(
@@ -46,7 +120,7 @@ content::BrowserMainParts* HeadlessContentBrowserClient::CreateBrowserMainParts(
void HeadlessContentBrowserClient::OverrideWebkitPrefs(
content::RenderViewHost* render_view_host,
content::WebPreferences* prefs) {
- auto browser_context = HeadlessBrowserContextImpl::From(
+ auto* browser_context = HeadlessBrowserContextImpl::From(
render_view_host->GetProcess()->GetBrowserContext());
const base::Callback<void(headless::WebPreferences*)>& callback =
browser_context->options()->override_web_preferences_callback();
@@ -86,4 +160,39 @@ HeadlessContentBrowserClient::GetServiceManifestOverlay(
return manifest;
}
+void HeadlessContentBrowserClient::GetQuotaSettings(
+ content::BrowserContext* context,
+ content::StoragePartition* partition,
+ const storage::OptionalQuotaSettingsCallback& callback) {
+ content::BrowserThread::PostTaskAndReplyWithResult(
+ content::BrowserThread::FILE, FROM_HERE,
+ base::Bind(&storage::CalculateNominalDynamicSettings,
+ partition->GetPath(), context->IsOffTheRecord()),
+ callback);
+}
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+void HeadlessContentBrowserClient::GetAdditionalMappedFilesForChildProcess(
+ const base::CommandLine& command_line,
+ int child_process_id,
+ content::FileDescriptorInfo* mappings) {
+#if defined(HEADLESS_USE_BREAKPAD)
+ int crash_signal_fd = GetCrashSignalFD(command_line, *browser_->options());
+ if (crash_signal_fd >= 0)
+ mappings->Share(kCrashDumpSignal, crash_signal_fd);
+#endif // defined(HEADLESS_USE_BREAKPAD)
+}
+#endif // defined(OS_POSIX) && !defined(OS_MACOSX)
+
+void HeadlessContentBrowserClient::AppendExtraCommandLineSwitches(
+ base::CommandLine* command_line,
+ int child_process_id) {
+ command_line->AppendSwitch(switches::kHeadless);
+#if defined(HEADLESS_USE_BREAKPAD)
+ // This flag tells child processes to also turn on crash reporting.
+ if (breakpad::IsCrashReporterEnabled())
+ command_line->AppendSwitch(switches::kEnableCrashReporter);
+#endif // defined(HEADLESS_USE_BREAKPAD)
+}
+
} // namespace headless
diff --git a/chromium/headless/lib/browser/headless_content_browser_client.h b/chromium/headless/lib/browser/headless_content_browser_client.h
index 3f443819867..53a950406e1 100644
--- a/chromium/headless/lib/browser/headless_content_browser_client.h
+++ b/chromium/headless/lib/browser/headless_content_browser_client.h
@@ -24,6 +24,18 @@ class HeadlessContentBrowserClient : public content::ContentBrowserClient {
content::DevToolsManagerDelegate* GetDevToolsManagerDelegate() override;
std::unique_ptr<base::Value> GetServiceManifestOverlay(
base::StringPiece name) override;
+ void GetQuotaSettings(
+ content::BrowserContext* context,
+ content::StoragePartition* partition,
+ const storage::OptionalQuotaSettingsCallback& callback) override;
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+ void GetAdditionalMappedFilesForChildProcess(
+ const base::CommandLine& command_line,
+ int child_process_id,
+ content::FileDescriptorInfo* mappings) override;
+#endif
+ void AppendExtraCommandLineSwitches(base::CommandLine* command_line,
+ int child_process_id) override;
private:
HeadlessBrowserImpl* browser_; // Not owned.
diff --git a/chromium/headless/lib/browser/headless_devtools.cc b/chromium/headless/lib/browser/headless_devtools.cc
index ea97afcc96a..88b9656a763 100644
--- a/chromium/headless/lib/browser/headless_devtools.cc
+++ b/chromium/headless/lib/browser/headless_devtools.cc
@@ -56,15 +56,14 @@ class TCPServerSocketFactory : public content::DevToolsSocketFactory {
} // namespace
-void StartLocalDevToolsHttpHandler(
- HeadlessBrowser::Options* options) {
+void StartLocalDevToolsHttpHandler(HeadlessBrowser::Options* options) {
const net::IPEndPoint& endpoint = options->devtools_endpoint;
std::unique_ptr<content::DevToolsSocketFactory> socket_factory(
new TCPServerSocketFactory(endpoint));
content::DevToolsAgentHost::StartRemoteDebuggingServer(
std::move(socket_factory), std::string(),
options->user_data_dir, // TODO(altimin): Figure a proper value for this.
- base::FilePath(), std::string(), options->user_agent);
+ base::FilePath(), options->product_name_and_version, options->user_agent);
}
void StopLocalDevToolsHttpHandler() {
diff --git a/chromium/headless/lib/browser/headless_focus_client.cc b/chromium/headless/lib/browser/headless_focus_client.cc
new file mode 100644
index 00000000000..9a4e806e86c
--- /dev/null
+++ b/chromium/headless/lib/browser/headless_focus_client.cc
@@ -0,0 +1,63 @@
+// Copyright 2017 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_focus_client.h"
+
+#include "ui/aura/client/focus_change_observer.h"
+#include "ui/aura/window.h"
+
+namespace headless {
+
+HeadlessFocusClient::HeadlessFocusClient()
+ : focused_window_(NULL), observer_manager_(this) {}
+
+HeadlessFocusClient::~HeadlessFocusClient() {}
+
+void HeadlessFocusClient::AddObserver(
+ aura::client::FocusChangeObserver* observer) {
+ focus_observers_.AddObserver(observer);
+}
+
+void HeadlessFocusClient::RemoveObserver(
+ aura::client::FocusChangeObserver* observer) {
+ focus_observers_.RemoveObserver(observer);
+}
+
+void HeadlessFocusClient::FocusWindow(aura::Window* window) {
+ if (window && !window->CanFocus())
+ return;
+
+ if (focused_window_)
+ observer_manager_.Remove(focused_window_);
+ aura::Window* old_focused_window = focused_window_;
+ focused_window_ = window;
+ if (focused_window_)
+ observer_manager_.Add(focused_window_);
+
+ for (aura::client::FocusChangeObserver& observer : focus_observers_)
+ observer.OnWindowFocused(focused_window_, old_focused_window);
+ aura::client::FocusChangeObserver* observer =
+ aura::client::GetFocusChangeObserver(old_focused_window);
+ if (observer)
+ observer->OnWindowFocused(focused_window_, old_focused_window);
+ observer = aura::client::GetFocusChangeObserver(focused_window_);
+ if (observer)
+ observer->OnWindowFocused(focused_window_, old_focused_window);
+}
+
+void HeadlessFocusClient::ResetFocusWithinActiveWindow(aura::Window* window) {
+ if (!window->Contains(focused_window_))
+ FocusWindow(window);
+}
+
+aura::Window* HeadlessFocusClient::GetFocusedWindow() {
+ return focused_window_;
+}
+
+void HeadlessFocusClient::OnWindowDestroying(aura::Window* window) {
+ DCHECK_EQ(window, focused_window_);
+ FocusWindow(NULL);
+}
+
+} // namespace headless
diff --git a/chromium/headless/lib/browser/headless_focus_client.h b/chromium/headless/lib/browser/headless_focus_client.h
new file mode 100644
index 00000000000..a218089ec7d
--- /dev/null
+++ b/chromium/headless/lib/browser/headless_focus_client.h
@@ -0,0 +1,42 @@
+// Copyright 2017 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_FOCUS_CLIENT_H_
+#define HEADLESS_LIB_BROWSER_HEADLESS_FOCUS_CLIENT_H_
+
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "base/scoped_observer.h"
+#include "ui/aura/client/focus_client.h"
+#include "ui/aura/window_observer.h"
+
+namespace headless {
+
+class HeadlessFocusClient : public aura::client::FocusClient,
+ public aura::WindowObserver {
+ public:
+ HeadlessFocusClient();
+ ~HeadlessFocusClient() override;
+
+ private:
+ // Overridden from aura::client::FocusClient:
+ void AddObserver(aura::client::FocusChangeObserver* observer) override;
+ void RemoveObserver(aura::client::FocusChangeObserver* observer) override;
+ void FocusWindow(aura::Window* window) override;
+ void ResetFocusWithinActiveWindow(aura::Window* window) override;
+ aura::Window* GetFocusedWindow() override;
+
+ // Overridden from aura::WindowObserver:
+ void OnWindowDestroying(aura::Window* window) override;
+
+ aura::Window* focused_window_;
+ ScopedObserver<aura::Window, aura::WindowObserver> observer_manager_;
+ base::ObserverList<aura::client::FocusChangeObserver> focus_observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(HeadlessFocusClient);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_LIB_BROWSER_HEADLESS_FOCUS_CLIENT_H_
diff --git a/chromium/headless/lib/browser/headless_screen.cc b/chromium/headless/lib/browser/headless_screen.cc
index 9cc1e4745a5..1d329885800 100644
--- a/chromium/headless/lib/browser/headless_screen.cc
+++ b/chromium/headless/lib/browser/headless_screen.cc
@@ -9,8 +9,6 @@
#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"
@@ -18,15 +16,6 @@
namespace headless {
-namespace {
-
-bool IsRotationPortrait(display::Display::Rotation rotation) {
- return rotation == display::Display::ROTATE_90 ||
- rotation == display::Display::ROTATE_270;
-}
-
-} // namespace
-
// static
HeadlessScreen* HeadlessScreen::Create(const gfx::Size& size) {
return new HeadlessScreen(gfx::Rect(size));
@@ -34,101 +23,6 @@ HeadlessScreen* HeadlessScreen::Create(const gfx::Size& size) {
HeadlessScreen::~HeadlessScreen() {}
-aura::WindowTreeHost* HeadlessScreen::CreateHostForPrimaryDisplay() {
- DCHECK(!host_);
- host_ = aura::WindowTreeHost::Create(
- gfx::Rect(GetPrimaryDisplay().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();
- host_->window()->Show();
- return host_;
-}
-
-void HeadlessScreen::SetDeviceScaleFactor(float device_scale_factor) {
- display::Display display(GetPrimaryDisplay());
- gfx::Rect bounds_in_pixel(display.GetSizeInPixel());
- display.SetScaleAndBounds(device_scale_factor, bounds_in_pixel);
- display_list().UpdateDisplay(display);
-}
-
-void HeadlessScreen::SetDisplayRotation(display::Display::Rotation rotation) {
- display::Display display(GetPrimaryDisplay());
- 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);
- display_list().UpdateDisplay(display);
- host_->SetRootTransform(GetRotationTransform() * GetUIScaleTransform());
-}
-
-void HeadlessScreen::SetUIScale(float ui_scale) {
- ui_scale_ = ui_scale;
- display::Display display(GetPrimaryDisplay());
- 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);
- display_list().UpdateDisplay(display);
- host_->SetRootTransform(GetRotationTransform() * GetUIScaleTransform());
-}
-
-void HeadlessScreen::SetWorkAreaInsets(const gfx::Insets& insets) {
- display::Display display(GetPrimaryDisplay());
- display.UpdateWorkAreaFromInsets(insets);
- display_list().UpdateDisplay(display);
-}
-
-gfx::Transform HeadlessScreen::GetRotationTransform() const {
- gfx::Transform rotate;
- display::Display display(GetPrimaryDisplay());
- switch (display.rotation()) {
- case display::Display::ROTATE_0:
- break;
- case display::Display::ROTATE_90:
- rotate.Translate(display.bounds().height(), 0);
- rotate.Rotate(90);
- break;
- case display::Display::ROTATE_270:
- rotate.Translate(0, display.bounds().width());
- rotate.Rotate(270);
- break;
- case display::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::Display display(GetPrimaryDisplay());
- display.SetSize(gfx::ScaleToFlooredSize(new_bounds.size(),
- display.device_scale_factor()));
- display_list().UpdateDisplay(display);
-}
-
-void HeadlessScreen::OnWindowDestroying(aura::Window* window) {
- if (host_->window() == window)
- host_ = NULL;
-}
-
gfx::Point HeadlessScreen::GetCursorScreenPoint() {
return aura::Env::GetInstance()->last_mouse_location();
}
@@ -139,18 +33,15 @@ bool HeadlessScreen::IsWindowUnderCursor(gfx::NativeWindow window) {
gfx::NativeWindow HeadlessScreen::GetWindowAtScreenPoint(
const gfx::Point& point) {
- if (!host_ || !host_->window())
- return nullptr;
- return host_->window()->GetTopWindowContainingPoint(point);
+ return nullptr;
}
display::Display HeadlessScreen::GetDisplayNearestWindow(
- gfx::NativeWindow window) const {
+ gfx::NativeView window) const {
return GetPrimaryDisplay();
}
-HeadlessScreen::HeadlessScreen(const gfx::Rect& screen_bounds)
- : host_(NULL), ui_scale_(1.0f) {
+HeadlessScreen::HeadlessScreen(const gfx::Rect& screen_bounds) {
static int64_t synthesized_display_id = 2000;
display::Display display(synthesized_display_id++);
display.SetScaleAndBounds(1.0f, screen_bounds);
diff --git a/chromium/headless/lib/browser/headless_screen.h b/chromium/headless/lib/browser/headless_screen.h
index 8c5cb98c083..b8331a67b0b 100644
--- a/chromium/headless/lib/browser/headless_screen.h
+++ b/chromium/headless/lib/browser/headless_screen.h
@@ -12,41 +12,18 @@
#include "ui/display/screen_base.h"
namespace gfx {
-class Insets;
class Rect;
-class Transform;
-}
-
-namespace aura {
-class Window;
-class WindowTreeHost;
}
namespace headless {
-class HeadlessScreen : public display::ScreenBase, public aura::WindowObserver {
+class HeadlessScreen : public display::ScreenBase {
public:
// Creates a display::Screen of the specified size (physical pixels).
static HeadlessScreen* Create(const gfx::Size& size);
~HeadlessScreen() override;
- aura::WindowTreeHost* CreateHostForPrimaryDisplay();
-
- void SetDeviceScaleFactor(float device_scale_fator);
- void SetDisplayRotation(display::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;
-
// display::Screen overrides:
gfx::Point GetCursorScreenPoint() override;
bool IsWindowUnderCursor(gfx::NativeWindow window) override;
@@ -56,9 +33,6 @@ class HeadlessScreen : public display::ScreenBase, public aura::WindowObserver {
private:
explicit HeadlessScreen(const gfx::Rect& screen_bounds);
- aura::WindowTreeHost* host_;
- float ui_scale_;
-
DISALLOW_COPY_AND_ASSIGN(HeadlessScreen);
};
diff --git a/chromium/headless/lib/browser/headless_web_contents_impl.cc b/chromium/headless/lib/browser/headless_web_contents_impl.cc
index 1516d964ff3..710fc84c4c9 100644
--- a/chromium/headless/lib/browser/headless_web_contents_impl.cc
+++ b/chromium/headless/lib/browser/headless_web_contents_impl.cc
@@ -21,7 +21,7 @@
#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/render_widget_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/bindings_policy.h"
@@ -31,7 +31,6 @@
#include "headless/lib/browser/headless_browser_main_parts.h"
#include "headless/lib/browser/headless_devtools_client_impl.h"
#include "services/service_manager/public/cpp/interface_registry.h"
-#include "ui/aura/window.h"
namespace headless {
@@ -99,6 +98,10 @@ class HeadlessWebContentsImpl::Delegate : public content::WebContentsDelegate {
security_style_explanations);
}
+ void ActivateContents(content::WebContents* contents) override {
+ contents->GetRenderViewHost()->GetWidget()->Focus();
+ }
+
private:
HeadlessBrowserContextImpl* browser_context_; // Not owned.
DISALLOW_COPY_AND_ASSIGN(Delegate);
@@ -106,8 +109,7 @@ class HeadlessWebContentsImpl::Delegate : public content::WebContentsDelegate {
// static
std::unique_ptr<HeadlessWebContentsImpl> HeadlessWebContentsImpl::Create(
- HeadlessWebContents::Builder* builder,
- aura::Window* parent_window) {
+ HeadlessWebContents::Builder* builder) {
content::WebContents::CreateParams create_params(builder->browser_context_,
nullptr);
create_params.initial_size = builder->window_size_;
@@ -118,7 +120,7 @@ std::unique_ptr<HeadlessWebContentsImpl> HeadlessWebContentsImpl::Create(
builder->browser_context_));
headless_web_contents->mojo_services_ = std::move(builder->mojo_services_);
- headless_web_contents->InitializeScreen(parent_window, builder->window_size_);
+ headless_web_contents->InitializeScreen(builder->window_size_);
if (!headless_web_contents->OpenURL(builder->initial_url_))
return nullptr;
return headless_web_contents;
@@ -136,18 +138,8 @@ HeadlessWebContentsImpl::CreateFromWebContents(
return headless_web_contents;
}
-void HeadlessWebContentsImpl::InitializeScreen(aura::Window* parent_window,
- const gfx::Size& initial_size) {
- 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);
+void HeadlessWebContentsImpl::InitializeScreen(const gfx::Size& initial_size) {
+ browser()->PlatformInitializeWebContents(initial_size, web_contents_.get());
}
HeadlessWebContentsImpl::HeadlessWebContentsImpl(
@@ -173,8 +165,7 @@ HeadlessWebContentsImpl::~HeadlessWebContentsImpl() {
void HeadlessWebContentsImpl::RenderFrameCreated(
content::RenderFrameHost* render_frame_host) {
if (!mojo_services_.empty()) {
- render_frame_host->GetRenderViewHost()->AllowBindings(
- content::BINDINGS_POLICY_HEADLESS);
+ render_frame_host->AllowBindings(content::BINDINGS_POLICY_HEADLESS);
}
service_manager::InterfaceRegistry* interface_registry =
diff --git a/chromium/headless/lib/browser/headless_web_contents_impl.h b/chromium/headless/lib/browser/headless_web_contents_impl.h
index a18a46aa49d..3346c64dca9 100644
--- a/chromium/headless/lib/browser/headless_web_contents_impl.h
+++ b/chromium/headless/lib/browser/headless_web_contents_impl.h
@@ -15,10 +15,6 @@
#include "headless/public/headless_devtools_target.h"
#include "headless/public/headless_web_contents.h"
-namespace aura {
-class Window;
-}
-
namespace content {
class DevToolsAgentHost;
class WebContents;
@@ -42,8 +38,7 @@ class HeadlessWebContentsImpl : public HeadlessWebContents,
static HeadlessWebContentsImpl* From(HeadlessWebContents* web_contents);
static std::unique_ptr<HeadlessWebContentsImpl> Create(
- HeadlessWebContents::Builder* builder,
- aura::Window* parent_window);
+ HeadlessWebContents::Builder* builder);
// Takes ownership of |web_contents|.
static std::unique_ptr<HeadlessWebContentsImpl> CreateFromWebContents(
@@ -85,8 +80,7 @@ class HeadlessWebContentsImpl : public HeadlessWebContents,
HeadlessWebContentsImpl(content::WebContents* web_contents,
HeadlessBrowserContextImpl* browser_context);
- void InitializeScreen(aura::Window* parent_window,
- const gfx::Size& initial_size);
+ void InitializeScreen(const gfx::Size& initial_size);
using MojoService = HeadlessWebContents::Builder::MojoService;
diff --git a/chromium/headless/lib/browser/headless_window_tree_host.cc b/chromium/headless/lib/browser/headless_window_tree_host.cc
index 96cec1a96e6..5cae74e7d48 100644
--- a/chromium/headless/lib/browser/headless_window_tree_host.cc
+++ b/chromium/headless/lib/browser/headless_window_tree_host.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "headless/lib/browser/headless_window_tree_host.h"
+#include "ui/aura/window.h"
#include "ui/gfx/icc_profile.h"
@@ -15,10 +16,15 @@ HeadlessWindowTreeHost::HeadlessWindowTreeHost(const gfx::Rect& bounds)
}
HeadlessWindowTreeHost::~HeadlessWindowTreeHost() {
+ window_parenting_client_.reset();
DestroyCompositor();
DestroyDispatcher();
}
+void HeadlessWindowTreeHost::SetParentWindow(gfx::NativeWindow window) {
+ window_parenting_client_.reset(new HeadlessWindowParentingClient(window));
+}
+
bool HeadlessWindowTreeHost::CanDispatchEvent(const ui::PlatformEvent& event) {
return false;
}
diff --git a/chromium/headless/lib/browser/headless_window_tree_host.h b/chromium/headless/lib/browser/headless_window_tree_host.h
index c01545c9c35..0aadfc87d18 100644
--- a/chromium/headless/lib/browser/headless_window_tree_host.h
+++ b/chromium/headless/lib/browser/headless_window_tree_host.h
@@ -5,7 +5,10 @@
#ifndef HEADLESS_LIB_BROWSER_HEADLESS_WINDOW_TREE_HOST_H_
#define HEADLESS_LIB_BROWSER_HEADLESS_WINDOW_TREE_HOST_H_
+#include <memory>
+
#include "base/macros.h"
+#include "headless/lib/browser/headless_window_parenting_client.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/platform/platform_event_dispatcher.h"
#include "ui/gfx/geometry/rect.h"
@@ -18,6 +21,8 @@ class HeadlessWindowTreeHost : public aura::WindowTreeHost,
explicit HeadlessWindowTreeHost(const gfx::Rect& bounds);
~HeadlessWindowTreeHost() override;
+ void SetParentWindow(gfx::NativeWindow window);
+
// ui::PlatformEventDispatcher:
bool CanDispatchEvent(const ui::PlatformEvent& event) override;
uint32_t DispatchEvent(const ui::PlatformEvent& event) override;
@@ -39,6 +44,7 @@ class HeadlessWindowTreeHost : public aura::WindowTreeHost,
private:
gfx::Rect bounds_;
+ std::unique_ptr<aura::client::WindowParentingClient> window_parenting_client_;
DISALLOW_COPY_AND_ASSIGN(HeadlessWindowTreeHost);
};
diff --git a/chromium/headless/lib/embed_data.py b/chromium/headless/lib/embed_data.py
new file mode 100644
index 00000000000..57ab26850ee
--- /dev/null
+++ b/chromium/headless/lib/embed_data.py
@@ -0,0 +1,96 @@
+# Copyright 2017 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.
+
+import argparse
+import sys
+import os
+
+COPYRIGHT="""// Copyright 2017 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.
+"""
+
+HEADER="""{copyright}
+
+#include "headless/lib/util/embedded_file.h"
+
+namespace {namespace} {{
+
+extern const headless::util::EmbeddedFile {variable_name};
+
+}} // namespace {namespace}
+"""
+
+SOURCE="""{copyright}
+
+#include "{header_file}"
+
+namespace {{
+
+const uint8_t contents[] = {contents};
+
+}} // anonymous namespace
+
+namespace {namespace} {{
+
+const headless::util::EmbeddedFile {variable_name} = {{ {length}, contents }};
+
+}} // namespace {namespace}
+"""
+
+def ParseArguments(args):
+ cmdline_parser = argparse.ArgumentParser()
+ cmdline_parser.add_argument('--data_file', required=True)
+ cmdline_parser.add_argument('--gendir', required=True)
+ cmdline_parser.add_argument('--header_file', required=True)
+ cmdline_parser.add_argument('--source_file', required=True)
+ cmdline_parser.add_argument('--namespace', required=True)
+ cmdline_parser.add_argument('--variable_name', required=True)
+
+ return cmdline_parser.parse_args(args)
+
+
+def GenerateArray(filepath):
+ with open(filepath, 'rb') as f:
+ contents = f.read()
+
+ contents = [ str(ord(char)) for char in contents ]
+
+ return len(contents), '{' + ','.join(contents) + '}'
+
+
+def GenerateHeader(args):
+ return HEADER.format(
+ copyright=COPYRIGHT,
+ namespace=args.namespace,
+ variable_name=args.variable_name)
+
+def GenerateSource(args):
+ length, contents = GenerateArray(args.data_file)
+
+ return SOURCE.format(
+ copyright=COPYRIGHT,
+ header_file=args.header_file,
+ namespace=args.namespace,
+ length=length,
+ contents=contents,
+ variable_name=args.variable_name)
+
+
+def WriteHeader(args):
+ with open(os.path.join(args.gendir, args.header_file), 'w') as f:
+ f.write(GenerateHeader(args))
+
+
+def WriteSource(args):
+ with open(os.path.join(args.gendir, args.source_file), 'w') as f:
+ f.write(GenerateSource(args))
+
+
+if __name__ == '__main__':
+ args = ParseArguments(sys.argv[1:])
+
+ WriteHeader(args)
+ WriteSource(args)
+
diff --git a/chromium/headless/lib/embedder_mojo_browsertest.cc b/chromium/headless/lib/embedder_mojo_browsertest.cc
index 92800f1acc3..17ce6ebac21 100644
--- a/chromium/headless/lib/embedder_mojo_browsertest.cc
+++ b/chromium/headless/lib/embedder_mojo_browsertest.cc
@@ -5,6 +5,7 @@
#include <memory>
#include "base/optional.h"
#include "base/path_service.h"
+#include "base/run_loop.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_restrictions.h"
@@ -66,7 +67,7 @@ class EmbedderMojoTest : public HeadlessBrowserTest,
pak_path, ui::SCALE_FACTOR_NONE);
}
- // HeadlessWebContentsObserver implementation:
+ // HeadlessWebContents::Observer implementation:
void DevToolsTargetReady() override {
EXPECT_TRUE(web_contents_->GetDevToolsTarget());
web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get());
@@ -225,12 +226,16 @@ class HttpDisabledByDefaultWhenMojoBindingsUsed : public EmbedderMojoTest,
}
void RunMojoTest() override {
+ base::RunLoop run_loop;
devtools_client_->GetNetwork()->AddObserver(this);
- devtools_client_->GetNetwork()->Enable();
- }
-
- GURL GetInitialUrl() const override {
- return embedded_test_server()->GetURL("/page_one.html");
+ devtools_client_->GetNetwork()->Enable(run_loop.QuitClosure());
+ base::MessageLoop::ScopedNestableTaskAllower nest_loop(
+ base::MessageLoop::current());
+ run_loop.Run();
+ devtools_client_->GetPage()->AddObserver(this);
+ devtools_client_->GetPage()->Enable();
+ devtools_client_->GetPage()->Navigate(
+ embedded_test_server()->GetURL("/page_one.html").spec());
}
void ReturnTestResult(const std::string& result) override {
diff --git a/chromium/headless/lib/headless_browser_browsertest.cc b/chromium/headless/lib/headless_browser_browsertest.cc
index 0bbf976849c..3c67da19387 100644
--- a/chromium/headless/lib/headless_browser_browsertest.cc
+++ b/chromium/headless/lib/headless_browser_browsertest.cc
@@ -5,11 +5,15 @@
#include <memory>
#include "base/command_line.h"
+#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_restrictions.h"
+#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test.h"
+#include "headless/lib/headless_macros.h"
+#include "headless/public/devtools/domains/inspector.h"
#include "headless/public/devtools/domains/network.h"
#include "headless/public/devtools/domains/page.h"
#include "headless/public/headless_browser.h"
@@ -574,7 +578,7 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, SetCookiesWithDevTools) {
.SetPath("")
.SetSecure(true)
.SetHttpOnly(true)
- .SetSameSite(network::CookieSameSite::STRICT)
+ .SetSameSite(network::CookieSameSite::EXACT)
.SetExpirationDate(0)
.Build();
CookieSetter cookie_setter(this, web_contents,
@@ -642,4 +646,88 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, MAYBE_RendererCommandPrefixTest) {
base::DeleteFile(launcher_stamp, false);
}
+class CrashReporterTest : public HeadlessBrowserTest,
+ public HeadlessWebContents::Observer,
+ inspector::ExperimentalObserver {
+ public:
+ CrashReporterTest() : devtools_client_(HeadlessDevToolsClient::Create()) {}
+ ~CrashReporterTest() override {}
+
+ void SetUp() override {
+ base::ThreadRestrictions::SetIOAllowed(true);
+ base::CreateNewTempDirectory("CrashReporterTest", &crash_dumps_dir_);
+ EXPECT_FALSE(options()->enable_crash_reporter);
+ options()->enable_crash_reporter = true;
+ options()->crash_dumps_dir = crash_dumps_dir_;
+ HeadlessBrowserTest::SetUp();
+ }
+
+ void TearDown() override {
+ base::ThreadRestrictions::SetIOAllowed(true);
+ base::DeleteFile(crash_dumps_dir_, /* recursive */ false);
+ }
+
+ // HeadlessWebContents::Observer implementation:
+ void DevToolsTargetReady() override {
+ EXPECT_TRUE(web_contents_->GetDevToolsTarget());
+ web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get());
+ devtools_client_->GetInspector()->GetExperimental()->AddObserver(this);
+ }
+
+ // inspector::ExperimentalObserver implementation:
+ void OnTargetCrashed(const inspector::TargetCrashedParams&) override {
+ FinishAsynchronousTest();
+ }
+
+ protected:
+ HeadlessBrowserContext* browser_context_ = nullptr;
+ HeadlessWebContents* web_contents_ = nullptr;
+ std::unique_ptr<HeadlessDevToolsClient> devtools_client_;
+ base::FilePath crash_dumps_dir_;
+};
+
+// TODO(skyostil): Minidump generation currently is only supported on Linux.
+#if defined(HEADLESS_USE_BREAKPAD)
+#define MAYBE_GenerateMinidump GenerateMinidump
+#else
+#define MAYBE_GenerateMinidump DISABLED_GenerateMinidump
+#endif // defined(HEADLESS_USE_BREAKPAD)
+IN_PROC_BROWSER_TEST_F(CrashReporterTest, MAYBE_GenerateMinidump) {
+ // Navigates a tab to chrome://crash and checks that a minidump is generated.
+ // Note that we only test renderer crashes here -- browser crashes need to be
+ // tested with a separate harness.
+ //
+ // The case where crash reporting is disabled is covered by
+ // HeadlessCrashObserverTest.
+ browser_context_ = browser()->CreateBrowserContextBuilder().Build();
+
+ web_contents_ = browser_context_->CreateWebContentsBuilder()
+ .SetInitialURL(GURL(content::kChromeUICrashURL))
+ .Build();
+
+ web_contents_->AddObserver(this);
+ RunAsynchronousTest();
+
+ // The target has crashed and should no longer be there.
+ EXPECT_FALSE(web_contents_->GetDevToolsTarget());
+
+ // Check that one minidump got created.
+ {
+ base::ThreadRestrictions::SetIOAllowed(true);
+ base::FileEnumerator it(crash_dumps_dir_, /* recursive */ false,
+ base::FileEnumerator::FILES);
+ base::FilePath minidump = it.Next();
+ EXPECT_FALSE(minidump.empty());
+ EXPECT_EQ(".dmp", minidump.Extension());
+ EXPECT_TRUE(it.Next().empty());
+ }
+
+ web_contents_->RemoveObserver(this);
+ web_contents_->Close();
+ web_contents_ = nullptr;
+
+ browser_context_->Close();
+ browser_context_ = nullptr;
+}
+
} // namespace headless
diff --git a/chromium/headless/lib/headless_browser_context_browsertest.cc b/chromium/headless/lib/headless_browser_context_browsertest.cc
index 49ab195c094..b7bfa6fcd56 100644
--- a/chromium/headless/lib/headless_browser_context_browsertest.cc
+++ b/chromium/headless/lib/headless_browser_context_browsertest.cc
@@ -196,7 +196,7 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, ContextProtocolHandler) {
another_browser_context->CreateWebContentsBuilder()
.SetInitialURL(GURL("http://not-an-actual-domain.tld/hello.html"))
.Build();
- EXPECT_TRUE(WaitForLoad(web_contents));
+ EXPECT_FALSE(WaitForLoad(web_contents));
EXPECT_TRUE(EvaluateScript(web_contents, "document.body.innerHTML")
->GetResult()
->GetValue()
diff --git a/chromium/headless/lib/headless_content_client.cc b/chromium/headless/lib/headless_content_client.cc
index 4269da62f0d..333f43a6ac9 100644
--- a/chromium/headless/lib/headless_content_client.cc
+++ b/chromium/headless/lib/headless_content_client.cc
@@ -14,6 +14,10 @@ HeadlessContentClient::HeadlessContentClient(HeadlessBrowser::Options* options)
HeadlessContentClient::~HeadlessContentClient() {}
+std::string HeadlessContentClient::GetProduct() const {
+ return options_->product_name_and_version;
+}
+
std::string HeadlessContentClient::GetUserAgent() const {
return options_->user_agent;
}
diff --git a/chromium/headless/lib/headless_content_client.h b/chromium/headless/lib/headless_content_client.h
index 56775bd0b4d..eb4d07d41b6 100644
--- a/chromium/headless/lib/headless_content_client.h
+++ b/chromium/headless/lib/headless_content_client.h
@@ -16,6 +16,7 @@ class HeadlessContentClient : public content::ContentClient {
~HeadlessContentClient() override;
// content::ContentClient implementation:
+ std::string GetProduct() const override;
std::string GetUserAgent() const override;
base::string16 GetLocalizedString(int message_id) const override;
base::StringPiece GetDataResource(
diff --git a/chromium/headless/lib/headless_content_main_delegate.cc b/chromium/headless/lib/headless_content_main_delegate.cc
index 4b7c1609035..72df5a29a65 100644
--- a/chromium/headless/lib/headless_content_main_delegate.cc
+++ b/chromium/headless/lib/headless_content_main_delegate.cc
@@ -4,21 +4,31 @@
#include "headless/lib/headless_content_main_delegate.h"
+#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
+#include "base/lazy_instance.h"
#include "base/path_service.h"
#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
#include "base/trace_event/trace_event.h"
+#include "components/crash/content/app/breakpad_linux.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/headless_crash_reporter_client.h"
+#include "headless/lib/headless_macros.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_switches.h"
#include "ui/gfx/switches.h"
#include "ui/gl/gl_switches.h"
#include "ui/ozone/public/ozone_switches.h"
+#ifdef HEADLESS_USE_EMBEDDED_RESOURCES
+#include "headless/embedded_resource_pak.h"
+#endif
+
namespace headless {
namespace {
// Keep in sync with content/common/content_constants_internal.h.
@@ -26,6 +36,9 @@ namespace {
const int kTraceEventBrowserProcessSortIndex = -6;
HeadlessContentMainDelegate* g_current_headless_content_main_delegate = nullptr;
+
+base::LazyInstance<HeadlessCrashReporterClient>::Leaky g_headless_crash_client =
+ LAZY_INSTANCE_INITIALIZER;
} // namespace
HeadlessContentMainDelegate::HeadlessContentMainDelegate(
@@ -70,7 +83,96 @@ bool HeadlessContentMainDelegate::BasicStartupComplete(int* exit_code) {
return false;
}
+void HeadlessContentMainDelegate::InitLogging(
+ const base::CommandLine& command_line) {
+#if !defined(OS_WIN)
+ if (!command_line.HasSwitch(switches::kEnableLogging))
+ return;
+#endif
+
+ logging::LoggingDestination log_mode;
+ base::FilePath log_filename(FILE_PATH_LITERAL("chrome_debug.log"));
+ if (command_line.GetSwitchValueASCII(switches::kEnableLogging) == "stderr") {
+ log_mode = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+ } else {
+ base::FilePath custom_filename(
+ command_line.GetSwitchValuePath(switches::kEnableLogging));
+ if (custom_filename.empty()) {
+ log_mode = logging::LOG_TO_ALL;
+ } else {
+ log_mode = logging::LOG_TO_FILE;
+ log_filename = custom_filename;
+ }
+ }
+
+ if (command_line.HasSwitch(switches::kLoggingLevel) &&
+ logging::GetMinLogLevel() >= 0) {
+ std::string log_level =
+ command_line.GetSwitchValueASCII(switches::kLoggingLevel);
+ int level = 0;
+ if (base::StringToInt(log_level, &level) && level >= 0 &&
+ level < logging::LOG_NUM_SEVERITIES) {
+ logging::SetMinLogLevel(level);
+ } else {
+ DLOG(WARNING) << "Bad log level: " << log_level;
+ }
+ }
+
+ base::FilePath log_path;
+ logging::LoggingSettings settings;
+
+ if (PathService::Get(base::DIR_MODULE, &log_path)) {
+ log_path = log_path.Append(log_filename);
+ } else {
+ log_path = log_filename;
+ }
+
+ const std::string process_type =
+ command_line.GetSwitchValueASCII(switches::kProcessType);
+
+ settings.logging_dest = log_mode;
+ settings.log_file = log_path.value().c_str();
+ settings.lock_log = logging::DONT_LOCK_LOG_FILE;
+ settings.delete_old = process_type.empty() ? logging::DELETE_OLD_LOG_FILE
+ : logging::APPEND_TO_OLD_LOG_FILE;
+ bool success = logging::InitLogging(settings);
+ DCHECK(success);
+}
+
+void HeadlessContentMainDelegate::InitCrashReporter(
+ const base::CommandLine& command_line) {
+ const std::string process_type =
+ command_line.GetSwitchValueASCII(switches::kProcessType);
+ crash_reporter::SetCrashReporterClient(g_headless_crash_client.Pointer());
+ g_headless_crash_client.Pointer()->set_crash_dumps_dir(
+ browser_->options()->crash_dumps_dir);
+
+#if !defined(OS_MACOSX)
+ if (!browser_->options()->enable_crash_reporter) {
+ DCHECK(!breakpad::IsCrashReporterEnabled());
+ return;
+ }
+#if defined(HEADLESS_USE_BREAKPAD)
+ if (process_type != switches::kZygoteProcess)
+ breakpad::InitCrashReporter(process_type);
+#endif // defined(HEADLESS_USE_BREAKPAD)
+#endif // !defined(OS_MACOSX)
+}
+
void HeadlessContentMainDelegate::PreSandboxStartup() {
+ const base::CommandLine& command_line(
+ *base::CommandLine::ForCurrentProcess());
+#if defined(OS_WIN)
+ // Windows always needs to initialize logging, otherwise you get a renderer
+ // crash.
+ InitLogging(command_line);
+#else
+ if (command_line.HasSwitch(switches::kEnableLogging))
+ InitLogging(command_line);
+#endif
+#if !defined(OS_MACOSX)
+ InitCrashReporter(command_line);
+#endif
InitializeResourceBundle();
}
@@ -100,9 +202,18 @@ int HeadlessContentMainDelegate::RunProcess(
return 0;
}
+#if !defined(OS_MACOSX) && defined(OS_POSIX) && !defined(OS_ANDROID)
void HeadlessContentMainDelegate::ZygoteForked() {
- // TODO(skyostil): Disable the zygote host.
+ const base::CommandLine& command_line(
+ *base::CommandLine::ForCurrentProcess());
+ const std::string process_type =
+ command_line.GetSwitchValueASCII(switches::kProcessType);
+ // Unconditionally try to turn on crash reporting since we do not have access
+ // to the latest browser options at this point when testing. Breakpad will
+ // bail out gracefully if the browser process hasn't enabled crash reporting.
+ breakpad::InitCrashReporter(process_type);
}
+#endif
// static
HeadlessContentMainDelegate* HeadlessContentMainDelegate::GetInstance() {
@@ -121,6 +232,13 @@ void HeadlessContentMainDelegate::InitializeResourceBundle() {
ui::ResourceBundle::InitSharedInstanceWithLocale(
locale, nullptr, ui::ResourceBundle::DO_NOT_LOAD_COMMON_RESOURCES);
+#ifdef HEADLESS_USE_EMBEDDED_RESOURCES
+ ResourceBundle::GetSharedInstance().AddDataPackFromBuffer(
+ base::StringPiece(
+ reinterpret_cast<const char*>(kHeadlessResourcePak.contents),
+ kHeadlessResourcePak.length),
+ ui::SCALE_FACTOR_NONE);
+#else
// Try loading the headless library pak file first. If it doesn't exist (i.e.,
// when we're running with the --headless switch), fall back to the browser's
// resource pak.
@@ -129,6 +247,7 @@ void HeadlessContentMainDelegate::InitializeResourceBundle() {
pak_file = dir_module.Append(FILE_PATH_LITERAL("resources.pak"));
ResourceBundle::GetSharedInstance().AddDataPackFromPath(
pak_file, ui::SCALE_FACTOR_NONE);
+#endif
}
content::ContentBrowserClient*
diff --git a/chromium/headless/lib/headless_content_main_delegate.h b/chromium/headless/lib/headless_content_main_delegate.h
index 2e7aafe7973..cb33f226d17 100644
--- a/chromium/headless/lib/headless_content_main_delegate.h
+++ b/chromium/headless/lib/headless_content_main_delegate.h
@@ -6,6 +6,7 @@
#define HEADLESS_LIB_HEADLESS_CONTENT_MAIN_DELEGATE_H_
#include <memory>
+#include <string>
#include "base/compiler_specific.h"
#include "base/macros.h"
@@ -13,6 +14,10 @@
#include "headless/lib/browser/headless_platform_event_source.h"
#include "headless/lib/headless_content_client.h"
+namespace base {
+class CommandLine;
+}
+
namespace headless {
class HeadlessBrowserImpl;
@@ -30,14 +35,19 @@ class HeadlessContentMainDelegate : public content::ContentMainDelegate {
int RunProcess(
const std::string& process_type,
const content::MainFunctionParams& main_function_params) override;
- void ZygoteForked() override;
content::ContentBrowserClient* CreateContentBrowserClient() override;
HeadlessBrowserImpl* browser() const { return browser_.get(); }
+#if !defined(OS_MACOSX) && defined(OS_POSIX) && !defined(OS_ANDROID)
+ void ZygoteForked() override;
+#endif
+
private:
friend class HeadlessBrowserTest;
+ void InitLogging(const base::CommandLine& command_line);
+ void InitCrashReporter(const base::CommandLine& command_line);
static void InitializeResourceBundle();
static HeadlessContentMainDelegate* GetInstance();
diff --git a/chromium/headless/lib/headless_crash_reporter_client.cc b/chromium/headless/lib/headless_crash_reporter_client.cc
new file mode 100644
index 00000000000..a319161dbd1
--- /dev/null
+++ b/chromium/headless/lib/headless_crash_reporter_client.cc
@@ -0,0 +1,63 @@
+// Copyright 2017 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_crash_reporter_client.h"
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/path_service.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "content/public/common/content_switches.h"
+#include "headless/public/version.h"
+
+namespace headless {
+
+HeadlessCrashReporterClient::HeadlessCrashReporterClient() = default;
+HeadlessCrashReporterClient::~HeadlessCrashReporterClient() = default;
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+void HeadlessCrashReporterClient::GetProductNameAndVersion(
+ const char** product_name,
+ const char** version) {
+ *product_name = "HeadlessChrome";
+ *version = PRODUCT_VERSION;
+}
+
+base::FilePath HeadlessCrashReporterClient::GetReporterLogFilename() {
+ return base::FilePath(FILE_PATH_LITERAL("uploads.log"));
+}
+#endif // defined(OS_POSIX) && !defined(OS_MACOSX)
+
+bool HeadlessCrashReporterClient::GetCrashDumpLocation(
+#if defined(OS_WIN)
+ base::string16* crash_dir
+#else
+ base::FilePath* crash_dir
+#endif
+ ) {
+ base::FilePath crash_directory = crash_dumps_dir_;
+ if (crash_directory.empty() &&
+ !base::PathService::Get(base::DIR_MODULE, &crash_directory)) {
+ return false;
+ }
+#if defined(OS_WIN)
+ *crash_dir = crash_directory.value();
+#else
+ *crash_dir = std::move(crash_directory);
+#endif
+ return true;
+}
+
+bool HeadlessCrashReporterClient::EnableBreakpadForProcess(
+ const std::string& process_type) {
+ return process_type == switches::kRendererProcess ||
+ process_type == switches::kPpapiPluginProcess ||
+ process_type == switches::kZygoteProcess ||
+ process_type == switches::kGpuProcess;
+}
+
+} // namespace content
diff --git a/chromium/headless/lib/headless_crash_reporter_client.h b/chromium/headless/lib/headless_crash_reporter_client.h
new file mode 100644
index 00000000000..42dc35bb232
--- /dev/null
+++ b/chromium/headless/lib/headless_crash_reporter_client.h
@@ -0,0 +1,51 @@
+// Copyright 2017 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_CRASH_REPORTER_CLIENT_H_
+#define HEADLESS_LIB_HEADLESS_CRASH_REPORTER_CLIENT_H_
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "build/build_config.h"
+#include "components/crash/content/app/crash_reporter_client.h"
+
+namespace headless {
+
+class HeadlessCrashReporterClient : public crash_reporter::CrashReporterClient {
+ public:
+ HeadlessCrashReporterClient();
+ ~HeadlessCrashReporterClient() override;
+
+ void set_crash_dumps_dir(const base::FilePath& dir) {
+ crash_dumps_dir_ = dir;
+ }
+ const base::FilePath& crash_dumps_dir() const { return crash_dumps_dir_; }
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+ // Returns a textual description of the product type and version to include
+ // in the crash report.
+ void GetProductNameAndVersion(const char** product_name,
+ const char** version) override;
+
+ base::FilePath GetReporterLogFilename() override;
+#endif // defined(OS_POSIX) && !defined(OS_MACOSX)
+
+#if defined(OS_WIN)
+ bool GetCrashDumpLocation(base::string16* crash_dir) override;
+#else
+ bool GetCrashDumpLocation(base::FilePath* crash_dir) override;
+#endif
+
+ bool EnableBreakpadForProcess(const std::string& process_type) override;
+
+ private:
+ base::FilePath crash_dumps_dir_;
+
+ DISALLOW_COPY_AND_ASSIGN(HeadlessCrashReporterClient);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_LIB_HEADLESS_CRASH_REPORTER_CLIENT_H_
diff --git a/chromium/headless/lib/headless_devtools_client_browsertest.cc b/chromium/headless/lib/headless_devtools_client_browsertest.cc
index 6dbd79fad28..0bb4bf1be6a 100644
--- a/chromium/headless/lib/headless_devtools_client_browsertest.cc
+++ b/chromium/headless/lib/headless_devtools_client_browsertest.cc
@@ -5,6 +5,7 @@
#include <memory>
#include "base/json/json_reader.h"
+#include "base/run_loop.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
@@ -21,6 +22,7 @@
#include "headless/public/headless_devtools_client.h"
#include "headless/public/headless_devtools_target.h"
#include "headless/test/headless_browser_test.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
@@ -30,6 +32,8 @@
EXPECT_EQ((expected).height(), (actual).height()); \
} while (false)
+using testing::ElementsAre;
+
namespace headless {
namespace {
@@ -60,7 +64,11 @@ class HeadlessDevToolsClientNavigationTest
.SetUrl(embedded_test_server()->GetURL("/hello.html").spec())
.Build();
devtools_client_->GetPage()->GetExperimental()->AddObserver(this);
- devtools_client_->GetPage()->Enable();
+ base::RunLoop run_loop;
+ base::MessageLoop::ScopedNestableTaskAllower nest_loop(
+ base::MessageLoop::current());
+ devtools_client_->GetPage()->Enable(run_loop.QuitClosure());
+ run_loop.Run();
devtools_client_->GetPage()->Navigate(std::move(params));
}
@@ -153,8 +161,13 @@ class HeadlessDevToolsClientObserverTest
public:
void RunDevTooledTest() override {
EXPECT_TRUE(embedded_test_server()->Start());
+ base::RunLoop run_loop;
devtools_client_->GetNetwork()->AddObserver(this);
- devtools_client_->GetNetwork()->Enable();
+ devtools_client_->GetNetwork()->Enable(run_loop.QuitClosure());
+ base::MessageLoop::ScopedNestableTaskAllower nest_loop(
+ base::MessageLoop::current());
+ run_loop.Run();
+
devtools_client_->GetPage()->Navigate(
embedded_test_server()->GetURL("/hello.html").spec());
}
@@ -189,6 +202,12 @@ class HeadlessDevToolsClientExperimentalTest
public:
void RunDevTooledTest() override {
EXPECT_TRUE(embedded_test_server()->Start());
+ base::RunLoop run_loop;
+ devtools_client_->GetPage()->GetExperimental()->AddObserver(this);
+ devtools_client_->GetPage()->Enable(run_loop.QuitClosure());
+ base::MessageLoop::ScopedNestableTaskAllower nest_loop(
+ base::MessageLoop::current());
+ run_loop.Run();
// Check that experimental commands require parameter objects.
devtools_client_->GetRuntime()
->GetExperimental()
@@ -202,8 +221,6 @@ class HeadlessDevToolsClientExperimentalTest
devtools_client_->GetRuntime()->GetExperimental()->RunIfWaitingForDebugger(
runtime::RunIfWaitingForDebuggerParams::Builder().Build());
- devtools_client_->GetPage()->GetExperimental()->AddObserver(this);
- devtools_client_->GetPage()->Enable();
devtools_client_->GetPage()->Navigate(
embedded_test_server()->GetURL("/hello.html").spec());
}
@@ -635,8 +652,12 @@ class HeadlessDevToolsNavigationControlTest
public:
void RunDevTooledTest() override {
EXPECT_TRUE(embedded_test_server()->Start());
+ base::RunLoop run_loop;
devtools_client_->GetPage()->GetExperimental()->AddObserver(this);
- devtools_client_->GetPage()->Enable();
+ devtools_client_->GetPage()->Enable(run_loop.QuitClosure());
+ base::MessageLoop::ScopedNestableTaskAllower nest_loop(
+ base::MessageLoop::current());
+ run_loop.Run();
devtools_client_->GetPage()->GetExperimental()->SetControlNavigations(
headless::page::SetControlNavigationsParams::Builder()
.SetEnabled(true)
@@ -758,8 +779,12 @@ class HeadlessDevToolsMethodCallErrorTest
public:
void RunDevTooledTest() override {
EXPECT_TRUE(embedded_test_server()->Start());
+ base::RunLoop run_loop;
devtools_client_->GetPage()->AddObserver(this);
- devtools_client_->GetPage()->Enable();
+ devtools_client_->GetPage()->Enable(run_loop.QuitClosure());
+ base::MessageLoop::ScopedNestableTaskAllower nest_loop(
+ base::MessageLoop::current());
+ run_loop.Run();
devtools_client_->GetPage()->Navigate(
embedded_test_server()->GetURL("/hello.html").spec());
}
@@ -789,4 +814,67 @@ class HeadlessDevToolsMethodCallErrorTest
HEADLESS_ASYNC_DEVTOOLED_TEST_F(HeadlessDevToolsMethodCallErrorTest);
+class HeadlessDevToolsNetworkBlockedUrlTest
+ : public HeadlessAsyncDevTooledBrowserTest,
+ public page::Observer,
+ public network::Observer {
+ public:
+ void RunDevTooledTest() override {
+ EXPECT_TRUE(embedded_test_server()->Start());
+ base::RunLoop run_loop;
+ devtools_client_->GetPage()->AddObserver(this);
+ devtools_client_->GetPage()->Enable();
+ devtools_client_->GetNetwork()->AddObserver(this);
+ devtools_client_->GetNetwork()->Enable(run_loop.QuitClosure());
+ base::MessageLoop::ScopedNestableTaskAllower nest_loop(
+ base::MessageLoop::current());
+ run_loop.Run();
+ devtools_client_->GetNetwork()->GetExperimental()->AddBlockedURL(
+ network::AddBlockedURLParams::Builder()
+ .SetUrl("dom_tree_test.css")
+ .Build());
+ devtools_client_->GetPage()->Navigate(
+ embedded_test_server()->GetURL("/dom_tree_test.html").spec());
+ }
+
+ std::string GetUrlPath(const std::string& url) const {
+ GURL gurl(url);
+ return gurl.path();
+ }
+
+ void OnRequestWillBeSent(
+ const network::RequestWillBeSentParams& params) override {
+ std::string path = GetUrlPath(params.GetRequest()->GetUrl());
+ requests_to_be_sent_.push_back(path);
+ request_id_to_path_[params.GetRequestId()] = path;
+ }
+
+ void OnResponseReceived(
+ const network::ResponseReceivedParams& params) override {
+ responses_received_.push_back(GetUrlPath(params.GetResponse()->GetUrl()));
+ }
+
+ void OnLoadingFailed(const network::LoadingFailedParams& failed) override {
+ failures_.push_back(request_id_to_path_[failed.GetRequestId()]);
+ EXPECT_EQ(network::BlockedReason::INSPECTOR, failed.GetBlockedReason());
+ }
+
+ void OnLoadEventFired(const page::LoadEventFiredParams&) override {
+ EXPECT_THAT(requests_to_be_sent_,
+ ElementsAre("/dom_tree_test.html", "/dom_tree_test.css",
+ "/iframe.html"));
+ EXPECT_THAT(responses_received_,
+ ElementsAre("/dom_tree_test.html", "/iframe.html"));
+ EXPECT_THAT(failures_, ElementsAre("/dom_tree_test.css"));
+ FinishAsynchronousTest();
+ }
+
+ std::map<std::string, std::string> request_id_to_path_;
+ std::vector<std::string> requests_to_be_sent_;
+ std::vector<std::string> responses_received_;
+ std::vector<std::string> failures_;
+};
+
+HEADLESS_ASYNC_DEVTOOLED_TEST_F(HeadlessDevToolsNetworkBlockedUrlTest);
+
} // namespace headless
diff --git a/chromium/headless/lib/headless_macros.h b/chromium/headless/lib/headless_macros.h
new file mode 100644
index 00000000000..4b7bedb7a9c
--- /dev/null
+++ b/chromium/headless/lib/headless_macros.h
@@ -0,0 +1,12 @@
+// Copyright 2017 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_MACROS_H_
+#define HEADLESS_LIB_HEADLESS_MACROS_H_
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+#define HEADLESS_USE_BREAKPAD
+#endif // defined(OS_POSIX) && !defined(OS_MACOSX)
+
+#endif // HEADLESS_LIB_HEADLESS_MACROS_H_
diff --git a/chromium/headless/lib/headless_web_contents_browsertest.cc b/chromium/headless/lib/headless_web_contents_browsertest.cc
index 693e2dceeaa..3bb62652ee9 100644
--- a/chromium/headless/lib/headless_web_contents_browsertest.cc
+++ b/chromium/headless/lib/headless_web_contents_browsertest.cc
@@ -61,6 +61,45 @@ IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, WindowOpen) {
browser_context->GetAllWebContents().size());
}
+IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, Focus) {
+ EXPECT_TRUE(embedded_test_server()->Start());
+
+ HeadlessBrowserContext* browser_context =
+ browser()->CreateBrowserContextBuilder().Build();
+
+ HeadlessWebContents* web_contents =
+ browser_context->CreateWebContentsBuilder()
+ .SetInitialURL(embedded_test_server()->GetURL("/hello.html"))
+ .Build();
+ EXPECT_TRUE(WaitForLoad(web_contents));
+
+ bool result;
+ EXPECT_TRUE(EvaluateScript(web_contents, "document.hasFocus()")
+ ->GetResult()
+ ->GetValue()
+ ->GetAsBoolean(&result));
+ EXPECT_TRUE(result);
+
+ HeadlessWebContents* web_contents2 =
+ browser_context->CreateWebContentsBuilder()
+ .SetInitialURL(embedded_test_server()->GetURL("/hello.html"))
+ .Build();
+ EXPECT_TRUE(WaitForLoad(web_contents2));
+
+ // TODO(irisu): Focus of two web contents should be independent of the other.
+ // Both web_contents and web_contents2 should be focused at this point.
+ EXPECT_TRUE(EvaluateScript(web_contents, "document.hasFocus()")
+ ->GetResult()
+ ->GetValue()
+ ->GetAsBoolean(&result));
+ EXPECT_FALSE(result);
+ EXPECT_TRUE(EvaluateScript(web_contents2, "document.hasFocus()")
+ ->GetResult()
+ ->GetValue()
+ ->GetAsBoolean(&result));
+ EXPECT_TRUE(result);
+}
+
namespace {
bool DecodePNG(std::string base64_data, SkBitmap* bitmap) {
std::string png_data;
diff --git a/chromium/headless/lib/util/embedded_file.h b/chromium/headless/lib/util/embedded_file.h
new file mode 100644
index 00000000000..4647aefec0a
--- /dev/null
+++ b/chromium/headless/lib/util/embedded_file.h
@@ -0,0 +1,22 @@
+// Copyright 2017 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_UTIL_EMBEDDED_FILE_H_
+#define HEADLESS_LIB_UTIL_EMBEDDED_FILE_H_
+
+#include <cstdint>
+#include <cstdlib>
+
+namespace headless {
+namespace util {
+
+struct EmbeddedFile {
+ size_t length;
+ const uint8_t* contents;
+};
+
+} // namespace util
+} // namespace headless
+
+#endif // HEADLESS_LIB_UTIL_EMBEDDED_FILE_H_
diff --git a/chromium/headless/public/domains/types_unittest.cc b/chromium/headless/public/domains/types_unittest.cc
index db07df1a58d..7d33c2b5db7 100644
--- a/chromium/headless/public/domains/types_unittest.cc
+++ b/chromium/headless/public/domains/types_unittest.cc
@@ -197,7 +197,7 @@ TEST(TypesTest, ObjectPropertyParseError) {
}
TEST(TypesTest, AnyProperty) {
- std::unique_ptr<base::Value> value(new base::FundamentalValue(123));
+ std::unique_ptr<base::Value> value(new base::Value(123));
std::unique_ptr<accessibility::AXValue> object(
accessibility::AXValue::Builder()
.SetType(accessibility::AXValueType::INTEGER)
diff --git a/chromium/headless/public/headless_browser.cc b/chromium/headless/public/headless_browser.cc
index 2b4270bd591..caa24e794d9 100644
--- a/chromium/headless/public/headless_browser.cc
+++ b/chromium/headless/public/headless_browser.cc
@@ -7,17 +7,22 @@
#include <utility>
#include "content/public/common/user_agent.h"
+#include "headless/public/version.h"
using Options = headless::HeadlessBrowser::Options;
using Builder = headless::HeadlessBrowser::Options::Builder;
namespace headless {
-// Product name for building the default user agent string.
namespace {
+// Product name for building the default user agent string.
const char kProductName[] = "HeadlessChrome";
constexpr gfx::Size kDefaultWindowSize(800, 600);
+
+std::string GetProductNameAndVersion() {
+ return std::string(kProductName) + "/" + PRODUCT_VERSION;
}
+} // namespace
Options::Options(int argc, const char** argv)
: argc(argc),
@@ -26,9 +31,11 @@ Options::Options(int argc, const char** argv)
single_process_mode(false),
disable_sandbox(false),
gl_implementation("osmesa"),
- user_agent(content::BuildUserAgentFromProduct(kProductName)),
+ product_name_and_version(GetProductNameAndVersion()),
+ user_agent(content::BuildUserAgentFromProduct(product_name_and_version)),
window_size(kDefaultWindowSize),
- incognito_mode(true) {}
+ incognito_mode(true),
+ enable_crash_reporter(false) {}
Options::Options(Options&& options) = default;
@@ -42,6 +49,12 @@ Builder::Builder() : options_(0, nullptr) {}
Builder::~Builder() {}
+Builder& Builder::SetProductNameAndVersion(
+ const std::string& product_name_and_version) {
+ options_.product_name_and_version = product_name_and_version;
+ return *this;
+}
+
Builder& Builder::SetUserAgent(const std::string& user_agent) {
options_.user_agent = user_agent;
return *this;
@@ -102,6 +115,22 @@ Builder& Builder::SetIncognitoMode(bool incognito_mode) {
return *this;
}
+Builder& Builder::SetOverrideWebPreferencesCallback(
+ base::Callback<void(WebPreferences*)> callback) {
+ options_.override_web_preferences_callback = callback;
+ return *this;
+}
+
+Builder& Builder::SetCrashReporterEnabled(bool enabled) {
+ options_.enable_crash_reporter = enabled;
+ return *this;
+}
+
+Builder& Builder::SetCrashDumpsDir(const base::FilePath& dir) {
+ options_.crash_dumps_dir = dir;
+ return *this;
+}
+
Options Builder::Build() {
return std::move(options_);
}
diff --git a/chromium/headless/public/headless_browser.h b/chromium/headless/public/headless_browser.h
index c3043f006ad..4b18f565b9e 100644
--- a/chromium/headless/public/headless_browser.h
+++ b/chromium/headless/public/headless_browser.h
@@ -128,6 +128,7 @@ struct HeadlessBrowser::Options {
// Default per-context options, can be specialized on per-context basis.
+ std::string product_name_and_version;
std::string user_agent;
// Address of the HTTP/HTTPS proxy server to use. The system proxy settings
@@ -149,6 +150,20 @@ struct HeadlessBrowser::Options {
// Run a browser context in an incognito mode. Enabled by default.
bool incognito_mode;
+ // Set a callback that is invoked to override WebPreferences for RenderViews
+ // created within the HeadlessBrowser. Called whenever the WebPreferences of a
+ // RenderView change. Executed on the browser main thread.
+ //
+ // WARNING: We cannot provide any guarantees about the stability of the
+ // exposed WebPreferences API, so use with care.
+ base::Callback<void(WebPreferences*)> override_web_preferences_callback;
+
+ // Minidump crash reporter settings. Crash reporting is disabled by default.
+ // By default crash dumps are written to the directory containing the
+ // executable.
+ bool enable_crash_reporter;
+ base::FilePath crash_dumps_dir;
+
// Reminder: when adding a new field here, do not forget to add it to
// HeadlessBrowserContextOptions (where appropriate).
private:
@@ -174,12 +189,18 @@ class HeadlessBrowser::Options::Builder {
// Per-context settings.
+ Builder& SetProductNameAndVersion(
+ const std::string& product_name_and_version);
Builder& SetUserAgent(const std::string& user_agent);
Builder& SetProxyServer(const net::HostPortPair& proxy_server);
Builder& SetHostResolverRules(const std::string& host_resolver_rules);
Builder& SetWindowSize(const gfx::Size& window_size);
Builder& SetUserDataDir(const base::FilePath& user_data_dir);
Builder& SetIncognitoMode(bool incognito_mode);
+ Builder& SetOverrideWebPreferencesCallback(
+ base::Callback<void(WebPreferences*)> callback);
+ Builder& SetCrashReporterEnabled(bool enabled);
+ Builder& SetCrashDumpsDir(const base::FilePath& dir);
Options Build();
diff --git a/chromium/headless/public/headless_browser_context.h b/chromium/headless/public/headless_browser_context.h
index b779f6a38da..4bdd7a51d7e 100644
--- a/chromium/headless/public/headless_browser_context.h
+++ b/chromium/headless/public/headless_browser_context.h
@@ -103,24 +103,19 @@ class HEADLESS_EXPORT HeadlessBrowserContext::Builder {
Builder& EnableUnsafeNetworkAccessWithMojoBindings(
bool enable_http_and_https_if_mojo_used);
- // Set a callback that is invoked to override WebPreferences for RenderViews
- // created within this HeadlessBrowserContext. Called whenever the
- // WebPreferences of a RenderView change. Executed on the browser main thread.
- //
- // WARNING: We cannot provide any guarantees about the stability of the
- // exposed WebPreferences API, so use with care.
- Builder& SetOverrideWebPreferencesCallback(
- base::Callback<void(WebPreferences*)> callback);
-
// By default |HeadlessBrowserContext| inherits the following options from
// the browser instance. The methods below can be used to override these
// settings. See HeadlessBrowser::Options for their meaning.
+ Builder& SetProductNameAndVersion(
+ const std::string& product_name_and_version);
Builder& SetUserAgent(const std::string& user_agent);
Builder& SetProxyServer(const net::HostPortPair& proxy_server);
Builder& SetHostResolverRules(const std::string& host_resolver_rules);
Builder& SetWindowSize(const gfx::Size& window_size);
Builder& SetUserDataDir(const base::FilePath& user_data_dir);
Builder& SetIncognitoMode(bool incognito_mode);
+ Builder& SetOverrideWebPreferencesCallback(
+ base::Callback<void(WebPreferences*)> callback);
HeadlessBrowserContext* Build();
diff --git a/chromium/headless/public/headless_shell.h b/chromium/headless/public/headless_shell.h
index f083d2030c1..0fd9066a80d 100644
--- a/chromium/headless/public/headless_shell.h
+++ b/chromium/headless/public/headless_shell.h
@@ -11,8 +11,7 @@ namespace headless {
// Start the Headless Shell application. Intended to be called early in main().
// Returns the exit code for the process.
-int HEADLESS_EXPORT HeadlessShellMain(int argc, const char** argv);
-
+HEADLESS_EXPORT int HeadlessShellMain(int argc, const char** argv);
} // namespace headless
#endif // HEADLESS_PUBLIC_HEADLESS_SHELL_H_
diff --git a/chromium/headless/public/internal/value_conversions.h b/chromium/headless/public/internal/value_conversions.h
index 78ac41fc4cb..948c94fee4d 100644
--- a/chromium/headless/public/internal/value_conversions.h
+++ b/chromium/headless/public/internal/value_conversions.h
@@ -32,17 +32,17 @@ struct FromValue {
// partially specialize vector types.
template <typename T>
std::unique_ptr<base::Value> ToValueImpl(int value, T*) {
- return base::MakeUnique<base::FundamentalValue>(value);
+ return base::MakeUnique<base::Value>(value);
}
template <typename T>
std::unique_ptr<base::Value> ToValueImpl(double value, T*) {
- return base::MakeUnique<base::FundamentalValue>(value);
+ return base::MakeUnique<base::Value>(value);
}
template <typename T>
std::unique_ptr<base::Value> ToValueImpl(bool value, T*) {
- return base::MakeUnique<base::FundamentalValue>(value);
+ return base::MakeUnique<base::Value>(value);
}
template <typename T>
diff --git a/chromium/headless/public/util/deterministic_dispatcher.cc b/chromium/headless/public/util/deterministic_dispatcher.cc
index 20982db898c..626279dd8b3 100644
--- a/chromium/headless/public/util/deterministic_dispatcher.cc
+++ b/chromium/headless/public/util/deterministic_dispatcher.cc
@@ -9,6 +9,7 @@
#include "base/bind.h"
#include "base/logging.h"
#include "headless/public/util/managed_dispatch_url_request_job.h"
+#include "headless/public/util/navigation_request.h"
namespace headless {
@@ -16,20 +17,21 @@ DeterministicDispatcher::DeterministicDispatcher(
scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner)
: io_thread_task_runner_(std::move(io_thread_task_runner)),
dispatch_pending_(false),
+ navigation_in_progress_(false),
weak_ptr_factory_(this) {}
DeterministicDispatcher::~DeterministicDispatcher() {}
void DeterministicDispatcher::JobCreated(ManagedDispatchURLRequestJob* job) {
base::AutoLock lock(lock_);
- pending_requests_.push_back(job);
+ pending_requests_.emplace_back(job);
}
void DeterministicDispatcher::JobKilled(ManagedDispatchURLRequestJob* job) {
base::AutoLock lock(lock_);
for (auto it = pending_requests_.begin(); it != pending_requests_.end();
it++) {
- if (*it == job) {
+ if (it->url_request == job) {
pending_requests_.erase(it);
break;
}
@@ -56,10 +58,28 @@ void DeterministicDispatcher::JobDeleted(ManagedDispatchURLRequestJob* job) {
MaybeDispatchJobLocked();
}
+void DeterministicDispatcher::NavigationRequested(
+ std::unique_ptr<NavigationRequest> navigation) {
+ base::AutoLock lock(lock_);
+ pending_requests_.emplace_back(std::move(navigation));
+
+ MaybeDispatchJobLocked();
+}
+
void DeterministicDispatcher::MaybeDispatchJobLocked() {
- if (dispatch_pending_ || ready_status_map_.empty())
+ if (dispatch_pending_ || navigation_in_progress_)
return;
+ if (ready_status_map_.empty()) {
+ if (pending_requests_.empty())
+ return; // Nothing to do.
+
+ // Don't post a task if the first job is a url request (which isn't ready
+ // yet).
+ if (pending_requests_.front().url_request)
+ return;
+ }
+
dispatch_pending_ = true;
io_thread_task_runner_->PostTask(
FROM_HERE,
@@ -68,8 +88,8 @@ void DeterministicDispatcher::MaybeDispatchJobLocked() {
}
void DeterministicDispatcher::MaybeDispatchJobOnIOThreadTask() {
- ManagedDispatchURLRequestJob* job;
- net::Error job_status;
+ Request request;
+ net::Error job_status = net::ERR_FAILED;
{
base::AutoLock lock(lock_);
@@ -77,22 +97,67 @@ void DeterministicDispatcher::MaybeDispatchJobOnIOThreadTask() {
// If the job got deleted, |pending_requests_| may be empty.
if (pending_requests_.empty())
return;
- job = pending_requests_.front();
- StatusMap::const_iterator it = ready_status_map_.find(job);
- // Bail out if the oldest job is not be ready for dispatch yet.
- if (it == ready_status_map_.end())
+
+ // Bail out if we're waiting for a navigation to complete.
+ if (navigation_in_progress_)
return;
- job_status = it->second;
- ready_status_map_.erase(it);
+ request = std::move(pending_requests_.front());
+ if (request.url_request) {
+ StatusMap::const_iterator it =
+ ready_status_map_.find(request.url_request);
+ // Bail out if the oldest job is not be ready for dispatch yet.
+ if (it == ready_status_map_.end())
+ return;
+
+ job_status = it->second;
+ ready_status_map_.erase(it);
+ } else {
+ DCHECK(!navigation_in_progress_);
+ navigation_in_progress_ = true;
+ }
pending_requests_.pop_front();
}
- if (job_status == net::OK) {
- job->OnHeadersComplete();
+ if (request.url_request) {
+ if (job_status == net::OK) {
+ request.url_request->OnHeadersComplete();
+ } else {
+ request.url_request->OnStartError(job_status);
+ }
} else {
- job->OnStartError(job_status);
+ request.navigation_request->StartProcessing(
+ base::Bind(&DeterministicDispatcher::NavigationDoneTask,
+ weak_ptr_factory_.GetWeakPtr()));
}
}
+void DeterministicDispatcher::NavigationDoneTask() {
+ {
+ base::AutoLock lock(lock_);
+ DCHECK(navigation_in_progress_);
+ navigation_in_progress_ = false;
+ }
+
+ MaybeDispatchJobLocked();
+}
+
+DeterministicDispatcher::Request::Request() : url_request(nullptr) {}
+DeterministicDispatcher::Request::~Request() {}
+
+DeterministicDispatcher::Request::Request(
+ ManagedDispatchURLRequestJob* url_request)
+ : url_request(url_request) {}
+
+DeterministicDispatcher::Request::Request(
+ std::unique_ptr<NavigationRequest> navigation_request)
+ : url_request(nullptr), navigation_request(std::move(navigation_request)) {}
+
+DeterministicDispatcher::Request& DeterministicDispatcher::Request::operator=(
+ DeterministicDispatcher::Request&& other) {
+ url_request = other.url_request;
+ navigation_request = std::move(other.navigation_request);
+ return *this;
+}
+
} // namespace headless
diff --git a/chromium/headless/public/util/deterministic_dispatcher.h b/chromium/headless/public/util/deterministic_dispatcher.h
index 0fea383b9cc..56c7c7e6390 100644
--- a/chromium/headless/public/util/deterministic_dispatcher.h
+++ b/chromium/headless/public/util/deterministic_dispatcher.h
@@ -20,8 +20,8 @@ namespace headless {
class ManagedDispatchURLRequestJob;
-// The purpose of this class is to queue up calls to OnHeadersComplete and
-// OnStartError and dispatch them in order of URLRequestJob creation. This
+// The purpose of this class is to queue up navigations and calls to
+// OnHeadersComplete / OnStartError and dispatch them in order of creation. This
// helps make renders deterministic at the cost of slower page loads.
class DeterministicDispatcher : public URLRequestDispatcher {
public:
@@ -36,17 +36,34 @@ class DeterministicDispatcher : public URLRequestDispatcher {
void JobFailed(ManagedDispatchURLRequestJob* job, net::Error error) override;
void DataReady(ManagedDispatchURLRequestJob* job) override;
void JobDeleted(ManagedDispatchURLRequestJob* job) override;
+ void NavigationRequested(
+ std::unique_ptr<NavigationRequest> navigation_request) override;
private:
+ void MaybeDispatchNavigationJobLocked();
void MaybeDispatchJobLocked();
void MaybeDispatchJobOnIOThreadTask();
+ void NavigationDoneTask();
scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner_;
// Protects all members below.
base::Lock lock_;
- std::deque<ManagedDispatchURLRequestJob*> pending_requests_;
+ // TODO(alexclarke): Use std::variant when c++17 is allowed in chromium.
+ struct Request {
+ Request();
+ explicit Request(ManagedDispatchURLRequestJob* url_request);
+ explicit Request(std::unique_ptr<NavigationRequest> navigation_request);
+ ~Request();
+
+ Request& operator=(Request&& other);
+
+ ManagedDispatchURLRequestJob* url_request; // NOT OWNED
+ std::unique_ptr<NavigationRequest> navigation_request;
+ };
+
+ std::deque<Request> pending_requests_;
using StatusMap = std::map<ManagedDispatchURLRequestJob*, net::Error>;
StatusMap ready_status_map_;
@@ -54,6 +71,7 @@ class DeterministicDispatcher : public URLRequestDispatcher {
// Whether or not a MaybeDispatchJobOnIoThreadTask has been posted on the
// |io_thread_task_runner_|
bool dispatch_pending_;
+ bool navigation_in_progress_;
base::WeakPtrFactory<DeterministicDispatcher> weak_ptr_factory_;
diff --git a/chromium/headless/public/util/deterministic_dispatcher_test.cc b/chromium/headless/public/util/deterministic_dispatcher_test.cc
index 24cede8b2e9..7c2c60494bd 100644
--- a/chromium/headless/public/util/deterministic_dispatcher_test.cc
+++ b/chromium/headless/public/util/deterministic_dispatcher_test.cc
@@ -8,8 +8,10 @@
#include <string>
#include <vector>
+#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
+#include "headless/public/util/navigation_request.h"
#include "headless/public/util/testing/fake_managed_dispatch_url_request_job.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -126,4 +128,70 @@ TEST_F(DeterministicDispatcherTest, JobKilled) {
EXPECT_TRUE(notifications.empty());
}
+namespace {
+class NavigationRequestForTest : public NavigationRequest {
+ public:
+ explicit NavigationRequestForTest(base::Closure* done_closure)
+ : done_closure_(done_closure) {}
+
+ ~NavigationRequestForTest() override {}
+
+ // NavigationRequest implementation:
+ void StartProcessing(base::Closure done_callback) override {
+ *done_closure_ = std::move(done_callback);
+ }
+
+ private:
+ base::Closure* done_closure_; // NOT OWNED
+};
+} // namespace
+
+TEST_F(DeterministicDispatcherTest, NavigationBlocksUrlRequests) {
+ std::vector<std::string> notifications;
+ std::unique_ptr<FakeManagedDispatchURLRequestJob> job1(
+ new FakeManagedDispatchURLRequestJob(deterministic_dispatcher_.get(), 1,
+ &notifications));
+ base::Closure navigation_done_closure;
+ deterministic_dispatcher_->NavigationRequested(
+ base::MakeUnique<NavigationRequestForTest>(&navigation_done_closure));
+ std::unique_ptr<FakeManagedDispatchURLRequestJob> job2(
+ new FakeManagedDispatchURLRequestJob(deterministic_dispatcher_.get(), 2,
+ &notifications));
+ std::unique_ptr<FakeManagedDispatchURLRequestJob> job3(
+ new FakeManagedDispatchURLRequestJob(deterministic_dispatcher_.get(), 3,
+ &notifications));
+ std::unique_ptr<FakeManagedDispatchURLRequestJob> job4(
+ new FakeManagedDispatchURLRequestJob(deterministic_dispatcher_.get(), 4,
+ &notifications));
+ job1->DispatchHeadersComplete();
+ job2->DispatchHeadersComplete();
+ job3->DispatchHeadersComplete();
+ job4->DispatchHeadersComplete();
+
+ EXPECT_TRUE(notifications.empty());
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_THAT(notifications, ElementsAre("id: 1 OK"));
+
+ // This triggers a call to NavigationRequestForTest::StartProcessing.
+ job1.reset();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_THAT(notifications, ElementsAre("id: 1 OK"));
+
+ // Navigations should be blocked until we call the done closure.
+ navigation_done_closure.Run();
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_THAT(notifications, ElementsAre("id: 1 OK", "id: 2 OK"));
+
+ job2.reset();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_THAT(notifications, ElementsAre("id: 1 OK", "id: 2 OK", "id: 3 OK"));
+
+ job3.reset();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_THAT(notifications,
+ ElementsAre("id: 1 OK", "id: 2 OK", "id: 3 OK", "id: 4 OK"));
+}
+
} // namespace headless
diff --git a/chromium/headless/public/util/deterministic_http_protocol_handler.cc b/chromium/headless/public/util/deterministic_http_protocol_handler.cc
index bb2c1117e21..6a186b99cf9 100644
--- a/chromium/headless/public/util/deterministic_http_protocol_handler.cc
+++ b/chromium/headless/public/util/deterministic_http_protocol_handler.cc
@@ -24,6 +24,7 @@ class DeterministicHttpProtocolHandler::NopGenericURLRequestJobDelegate
// GenericURLRequestJob::Delegate methods:
bool BlockOrRewriteRequest(
const GURL& url,
+ const std::string& devtools_id,
const std::string& method,
const std::string& referrer,
GenericURLRequestJob::RewriteCallback callback) override {
@@ -32,12 +33,14 @@ class DeterministicHttpProtocolHandler::NopGenericURLRequestJobDelegate
const GenericURLRequestJob::HttpResponse* MaybeMatchResource(
const GURL& url,
+ const std::string& devtools_id,
const std::string& method,
const net::HttpRequestHeaders& request_headers) override {
return nullptr;
}
void OnResourceLoadComplete(const GURL& final_url,
+ const std::string& devtools_id,
const std::string& mime_type,
int http_response_code) override {}
diff --git a/chromium/headless/public/util/dom_tree_extractor.h b/chromium/headless/public/util/dom_tree_extractor.h
index dcd08bfe917..1ba9bf39200 100644
--- a/chromium/headless/public/util/dom_tree_extractor.h
+++ b/chromium/headless/public/util/dom_tree_extractor.h
@@ -19,6 +19,7 @@ class HeadlessDevToolsClient;
// addition, it also extracts details of bounding boxes and layout text (NB the
// exact layout should not be regarded as stable, it's subject to change without
// notice).
+// TODO(alexclarke): Remove in favor of one using DOM.getFlattenedDocument.
class DomTreeExtractor {
public:
explicit DomTreeExtractor(HeadlessDevToolsClient* devtools_client);
diff --git a/chromium/headless/public/util/dom_tree_extractor_browsertest.cc b/chromium/headless/public/util/dom_tree_extractor_browsertest.cc
index 22b237960c5..cd1dc24d4bc 100644
--- a/chromium/headless/public/util/dom_tree_extractor_browsertest.cc
+++ b/chromium/headless/public/util/dom_tree_extractor_browsertest.cc
@@ -189,6 +189,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "HTML",
"nodeType": 1,
"nodeValue": "",
+ "parentId": 1,
"styleIndex": 0
})raw_string",
@@ -200,7 +201,8 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeId": 3,
"nodeName": "HEAD",
"nodeType": 1,
- "nodeValue": ""
+ "nodeValue": "",
+ "parentId": 2
})raw_string",
R"raw_string({
@@ -211,7 +213,8 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeId": 4,
"nodeName": "TITLE",
"nodeType": 1,
- "nodeValue": ""
+ "nodeValue": "",
+ "parentId": 3
})raw_string",
R"raw_string({
@@ -220,7 +223,8 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeId": 5,
"nodeName": "#text",
"nodeType": 3,
- "nodeValue": "Hello world!"
+ "nodeValue": "Hello world!",
+ "parentId": 4
})raw_string",
R"raw_string({
@@ -232,7 +236,8 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeId": 6,
"nodeName": "LINK",
"nodeType": 1,
- "nodeValue": ""
+ "nodeValue": "",
+ "parentId": 3
})raw_string",
R"raw_string({
@@ -250,6 +255,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "BODY",
"nodeType": 1,
"nodeValue": "",
+ "parentId": 2,
"styleIndex": 1
})raw_string",
@@ -268,6 +274,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "DIV",
"nodeType": 1,
"nodeValue": "",
+ "parentId": 7,
"styleIndex": 0
})raw_string",
@@ -286,6 +293,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "H1",
"nodeType": 1,
"nodeValue": "",
+ "parentId": 8,
"styleIndex": 2
})raw_string",
@@ -313,6 +321,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "#text",
"nodeType": 3,
"nodeValue": "Some text.",
+ "parentId": 9,
"styleIndex": 2
})raw_string",
@@ -334,6 +343,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "IFRAME",
"nodeType": 1,
"nodeValue": "",
+ "parentId": 8,
"styleIndex": 6
})raw_string",
@@ -366,6 +376,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "HTML",
"nodeType": 1,
"nodeValue": "",
+ "parentId": 12,
"styleIndex": 3
})raw_string",
@@ -377,7 +388,8 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeId": 14,
"nodeName": "HEAD",
"nodeType": 1,
- "nodeValue": ""
+ "nodeValue": "",
+ "parentId": 13
})raw_string",
R"raw_string({
@@ -395,6 +407,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "BODY",
"nodeType": 1,
"nodeValue": "",
+ "parentId": 13,
"styleIndex": 4
})raw_string",
@@ -413,6 +426,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "H1",
"nodeType": 1,
"nodeValue": "",
+ "parentId": 15,
"styleIndex": 5
})raw_string",
@@ -440,6 +454,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "#text",
"nodeType": 3,
"nodeValue": "Hello from the iframe!",
+ "parentId": 16,
"styleIndex": 5
})raw_string",
@@ -458,6 +473,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "DIV",
"nodeType": 1,
"nodeValue": "",
+ "parentId": 8,
"styleIndex": 0
})raw_string",
@@ -476,6 +492,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "DIV",
"nodeType": 1,
"nodeValue": "",
+ "parentId": 18,
"styleIndex": 0
})raw_string",
@@ -494,6 +511,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "DIV",
"nodeType": 1,
"nodeValue": "",
+ "parentId": 19,
"styleIndex": 0
})raw_string",
@@ -512,6 +530,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "A",
"nodeType": 1,
"nodeValue": "",
+ "parentId": 20,
"styleIndex": 7
})raw_string",
@@ -539,6 +558,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "#text",
"nodeType": 3,
"nodeValue": "Google!",
+ "parentId": 21,
"styleIndex": 7
})raw_string",
@@ -557,6 +577,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "P",
"nodeType": 1,
"nodeValue": "",
+ "parentId": 20,
"styleIndex": 8
})raw_string",
@@ -584,6 +605,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "#text",
"nodeType": 3,
"nodeValue": "A paragraph!",
+ "parentId": 23,
"styleIndex": 8
})raw_string",
@@ -613,6 +635,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "BR",
"nodeType": 1,
"nodeValue": "",
+ "parentId": 20,
"styleIndex": 6
})raw_string",
@@ -631,9 +654,9 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "DIV",
"nodeType": 1,
"nodeValue": "",
+ "parentId": 20,
"styleIndex": 9
- }
- )raw_string",
+ })raw_string",
R"raw_string({
"backendNodeId": 29,
@@ -659,6 +682,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "#text",
"nodeType": 3,
"nodeValue": "Some ",
+ "parentId": 26,
"styleIndex": 9
})raw_string",
@@ -677,6 +701,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "EM",
"nodeType": 1,
"nodeValue": "",
+ "parentId": 26,
"styleIndex": 10
})raw_string",
@@ -704,6 +729,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "#text",
"nodeType": 3,
"nodeValue": "green",
+ "parentId": 28,
"styleIndex": 10
})raw_string",
@@ -731,6 +757,7 @@ class DomTreeExtractorBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
"nodeName": "#text",
"nodeType": 3,
"nodeValue": " text...",
+ "parentId": 26,
"styleIndex": 9
})raw_string"};
diff --git a/chromium/headless/public/util/expedited_dispatcher.cc b/chromium/headless/public/util/expedited_dispatcher.cc
index 0120442dd0e..649321a7ad0 100644
--- a/chromium/headless/public/util/expedited_dispatcher.cc
+++ b/chromium/headless/public/util/expedited_dispatcher.cc
@@ -8,6 +8,7 @@
#include "base/bind.h"
#include "headless/public/util/managed_dispatch_url_request_job.h"
+#include "headless/public/util/navigation_request.h"
namespace headless {
@@ -36,4 +37,13 @@ void ExpeditedDispatcher::DataReady(ManagedDispatchURLRequestJob* job) {
void ExpeditedDispatcher::JobDeleted(ManagedDispatchURLRequestJob*) {}
+void ExpeditedDispatcher::NavigationRequested(
+ std::unique_ptr<NavigationRequest> navigation) {
+ // For the ExpeditedDispatcher we don't care when the navigation is done,
+ // hence the empty closure.
+ io_thread_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&NavigationRequest::StartProcessing,
+ std::move(navigation), base::Closure()));
+}
+
} // namespace headless
diff --git a/chromium/headless/public/util/expedited_dispatcher.h b/chromium/headless/public/util/expedited_dispatcher.h
index 39b8057fdfc..61062c4d51d 100644
--- a/chromium/headless/public/util/expedited_dispatcher.h
+++ b/chromium/headless/public/util/expedited_dispatcher.h
@@ -29,6 +29,8 @@ class ExpeditedDispatcher : public URLRequestDispatcher {
void JobFailed(ManagedDispatchURLRequestJob* job, net::Error error) override;
void DataReady(ManagedDispatchURLRequestJob* job) override;
void JobDeleted(ManagedDispatchURLRequestJob* job) override;
+ void NavigationRequested(
+ std::unique_ptr<NavigationRequest> navigation_request) override;
private:
scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner_;
diff --git a/chromium/headless/public/util/expedited_dispatcher_test.cc b/chromium/headless/public/util/expedited_dispatcher_test.cc
index 5ef6301ba1d..54f5249b650 100644
--- a/chromium/headless/public/util/expedited_dispatcher_test.cc
+++ b/chromium/headless/public/util/expedited_dispatcher_test.cc
@@ -8,8 +8,10 @@
#include <string>
#include <vector>
+#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
+#include "headless/public/util/navigation_request.h"
#include "headless/public/util/testing/fake_managed_dispatch_url_request_job.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -83,4 +85,51 @@ TEST_F(ExpeditedDispatcherTest, ErrorsAndDataReadyDispatchedInCallOrder) {
"id: 2 OK", "id: 1 OK"));
}
+namespace {
+class NavigationRequestForTest : public NavigationRequest {
+ public:
+ explicit NavigationRequestForTest(base::Closure* done_closure)
+ : done_closure_(done_closure) {}
+
+ ~NavigationRequestForTest() override {}
+
+ // NavigationRequest implementation:
+ void StartProcessing(base::Closure done_callback) override {
+ *done_closure_ = std::move(done_callback);
+ }
+
+ private:
+ base::Closure* done_closure_; // NOT OWNED
+};
+} // namespace
+
+TEST_F(ExpeditedDispatcherTest, NavigationDoesNotBlockUrlRequests) {
+ std::vector<std::string> notifications;
+ std::unique_ptr<FakeManagedDispatchURLRequestJob> job1(
+ new FakeManagedDispatchURLRequestJob(expedited_dispatcher_.get(), 1,
+ &notifications));
+ base::Closure navigation_done_closure;
+ expedited_dispatcher_->NavigationRequested(
+ base::MakeUnique<NavigationRequestForTest>(&navigation_done_closure));
+ std::unique_ptr<FakeManagedDispatchURLRequestJob> job2(
+ new FakeManagedDispatchURLRequestJob(expedited_dispatcher_.get(), 2,
+ &notifications));
+ std::unique_ptr<FakeManagedDispatchURLRequestJob> job3(
+ new FakeManagedDispatchURLRequestJob(expedited_dispatcher_.get(), 3,
+ &notifications));
+ std::unique_ptr<FakeManagedDispatchURLRequestJob> job4(
+ new FakeManagedDispatchURLRequestJob(expedited_dispatcher_.get(), 4,
+ &notifications));
+ job1->DispatchHeadersComplete();
+ job2->DispatchHeadersComplete();
+ job3->DispatchHeadersComplete();
+ job4->DispatchHeadersComplete();
+
+ EXPECT_TRUE(notifications.empty());
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_THAT(notifications,
+ ElementsAre("id: 1 OK", "id: 2 OK", "id: 3 OK", "id: 4 OK"));
+}
+
} // namespace headless
diff --git a/chromium/headless/public/util/flat_dom_tree_extractor.cc b/chromium/headless/public/util/flat_dom_tree_extractor.cc
new file mode 100644
index 00000000000..1ac9210aabc
--- /dev/null
+++ b/chromium/headless/public/util/flat_dom_tree_extractor.cc
@@ -0,0 +1,114 @@
+// Copyright 2017 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/public/util/flat_dom_tree_extractor.h"
+
+#include "base/bind.h"
+#include "base/json/json_writer.h"
+#include "headless/public/headless_devtools_client.h"
+
+namespace headless {
+
+FlatDomTreeExtractor::FlatDomTreeExtractor(
+ HeadlessDevToolsClient* devtools_client)
+ : work_in_progress_(false),
+ devtools_client_(devtools_client),
+ weak_factory_(this) {}
+
+FlatDomTreeExtractor::~FlatDomTreeExtractor() {}
+
+void FlatDomTreeExtractor::ExtractDomTree(
+ const std::vector<std::string>& css_style_whitelist,
+ DomResultCB callback) {
+ DCHECK(!work_in_progress_);
+ work_in_progress_ = true;
+
+ callback_ = std::move(callback);
+
+ devtools_client_->GetDOM()->Enable();
+ devtools_client_->GetDOM()->GetFlattenedDocument(
+ dom::GetFlattenedDocumentParams::Builder()
+ .SetDepth(-1)
+ .SetPierce(true)
+ .Build(),
+ base::Bind(&FlatDomTreeExtractor::OnDocumentFetched,
+ weak_factory_.GetWeakPtr()));
+
+ devtools_client_->GetCSS()->GetExperimental()->GetLayoutTreeAndStyles(
+ css::GetLayoutTreeAndStylesParams::Builder()
+ .SetComputedStyleWhitelist(css_style_whitelist)
+ .Build(),
+ base::Bind(&FlatDomTreeExtractor::OnLayoutTreeAndStylesFetched,
+ weak_factory_.GetWeakPtr()));
+}
+
+void FlatDomTreeExtractor::OnDocumentFetched(
+ std::unique_ptr<dom::GetFlattenedDocumentResult> result) {
+ dom_tree_.document_result_ = std::move(result);
+ MaybeExtractDomTree();
+}
+
+void FlatDomTreeExtractor::OnLayoutTreeAndStylesFetched(
+ std::unique_ptr<css::GetLayoutTreeAndStylesResult> result) {
+ dom_tree_.layout_tree_and_styles_result_ = std::move(result);
+ MaybeExtractDomTree();
+}
+
+void FlatDomTreeExtractor::MaybeExtractDomTree() {
+ if (dom_tree_.document_result_ && dom_tree_.layout_tree_and_styles_result_) {
+ for (const std::unique_ptr<headless::dom::Node>& node :
+ *dom_tree_.document_result_->GetNodes()) {
+ EnumerateNodes(node.get());
+ }
+ ExtractLayoutTreeNodes();
+ ExtractComputedStyles();
+ devtools_client_->GetDOM()->Disable();
+
+ work_in_progress_ = false;
+
+ callback_.Run(std::move(dom_tree_));
+ }
+}
+
+void FlatDomTreeExtractor::EnumerateNodes(const dom::Node* node) {
+ // Allocate an index and record the node pointer.
+ size_t index = dom_tree_.node_id_to_index_.size();
+ dom_tree_.node_id_to_index_[node->GetNodeId()] = index;
+ dom_tree_.dom_nodes_.push_back(node);
+
+ if (node->HasContentDocument())
+ EnumerateNodes(node->GetContentDocument());
+
+ DCHECK(!node->HasChildren() || node->GetChildren()->empty());
+}
+
+void FlatDomTreeExtractor::ExtractLayoutTreeNodes() {
+ dom_tree_.layout_tree_nodes_.reserve(
+ dom_tree_.layout_tree_and_styles_result_->GetLayoutTreeNodes()->size());
+
+ for (const std::unique_ptr<css::LayoutTreeNode>& layout_node :
+ *dom_tree_.layout_tree_and_styles_result_->GetLayoutTreeNodes()) {
+ std::unordered_map<NodeId, size_t>::const_iterator it =
+ dom_tree_.node_id_to_index_.find(layout_node->GetNodeId());
+ DCHECK(it != dom_tree_.node_id_to_index_.end());
+ dom_tree_.layout_tree_nodes_.push_back(layout_node.get());
+ }
+}
+
+void FlatDomTreeExtractor::ExtractComputedStyles() {
+ dom_tree_.computed_styles_.reserve(
+ dom_tree_.layout_tree_and_styles_result_->GetComputedStyles()->size());
+
+ for (const std::unique_ptr<css::ComputedStyle>& computed_style :
+ *dom_tree_.layout_tree_and_styles_result_->GetComputedStyles()) {
+ dom_tree_.computed_styles_.push_back(computed_style.get());
+ }
+}
+
+FlatDomTreeExtractor::DomTree::DomTree() {}
+FlatDomTreeExtractor::DomTree::~DomTree() {}
+
+FlatDomTreeExtractor::DomTree::DomTree(DomTree&& other) = default;
+
+} // namespace headless
diff --git a/chromium/headless/public/util/flat_dom_tree_extractor.h b/chromium/headless/public/util/flat_dom_tree_extractor.h
new file mode 100644
index 00000000000..14de70d8572
--- /dev/null
+++ b/chromium/headless/public/util/flat_dom_tree_extractor.h
@@ -0,0 +1,89 @@
+// Copyright 2017 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_UTIL_DOM_TREE_EXTRACTOR_H_
+#define HEADLESS_PUBLIC_UTIL_DOM_TREE_EXTRACTOR_H_
+
+#include <unordered_map>
+#include <vector>
+
+#include "base/macros.h"
+#include "headless/public/devtools/domains/css.h"
+#include "headless/public/devtools/domains/dom.h"
+
+namespace headless {
+class HeadlessDevToolsClient;
+
+// A utility class for extracting information from the DOM via DevTools. In
+// addition, it also extracts details of bounding boxes and layout text (NB the
+// exact layout should not be regarded as stable, it's subject to change without
+// notice).
+class FlatDomTreeExtractor {
+ public:
+ explicit FlatDomTreeExtractor(HeadlessDevToolsClient* devtools_client);
+ ~FlatDomTreeExtractor();
+
+ using NodeId = int;
+ using Index = size_t;
+
+ class DomTree {
+ public:
+ DomTree();
+ DomTree(DomTree&& other);
+ ~DomTree();
+
+ // Flattened dom tree. The root node is always the first entry.
+ std::vector<const dom::Node*> dom_nodes_;
+
+ // Map of node IDs to indexes into |dom_nodes_|.
+ std::unordered_map<NodeId, Index> node_id_to_index_;
+
+ std::vector<const css::LayoutTreeNode*> layout_tree_nodes_;
+
+ std::vector<const css::ComputedStyle*> computed_styles_;
+
+ private:
+ friend class FlatDomTreeExtractor;
+
+ // Owns the raw pointers in |dom_nodes_|.
+ std::unique_ptr<dom::GetFlattenedDocumentResult> document_result_;
+
+ // Owns the raw pointers in |layout_tree_nodes_|.
+ std::unique_ptr<css::GetLayoutTreeAndStylesResult>
+ layout_tree_and_styles_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(DomTree);
+ };
+
+ using DomResultCB = base::Callback<void(DomTree)>;
+
+ // Extracts all nodes from the DOM. This is an asynchronous operation and
+ // it's an error to call ExtractDom while a previous operation is in flight.
+ void ExtractDomTree(const std::vector<std::string>& css_style_whitelist,
+ DomResultCB callback);
+
+ private:
+ void OnDocumentFetched(
+ std::unique_ptr<dom::GetFlattenedDocumentResult> result);
+
+ void OnLayoutTreeAndStylesFetched(
+ std::unique_ptr<css::GetLayoutTreeAndStylesResult> result);
+
+ void MaybeExtractDomTree();
+ void EnumerateNodes(const dom::Node* node);
+ void ExtractLayoutTreeNodes();
+ void ExtractComputedStyles();
+
+ DomResultCB callback_;
+ DomTree dom_tree_;
+ bool work_in_progress_;
+ HeadlessDevToolsClient* devtools_client_; // NOT OWNED
+ base::WeakPtrFactory<FlatDomTreeExtractor> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FlatDomTreeExtractor);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_DOM_TREE_EXTRACTOR_H_
diff --git a/chromium/headless/public/util/flat_dom_tree_extractor_browsertest.cc b/chromium/headless/public/util/flat_dom_tree_extractor_browsertest.cc
new file mode 100644
index 00000000000..e897301cd3a
--- /dev/null
+++ b/chromium/headless/public/util/flat_dom_tree_extractor_browsertest.cc
@@ -0,0 +1,921 @@
+// Copyright 2017 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/public/util/flat_dom_tree_extractor.h"
+
+#include <memory>
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/strings/string_util.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test.h"
+#include "headless/lib/browser/headless_web_contents_impl.h"
+#include "headless/public/devtools/domains/emulation.h"
+#include "headless/public/devtools/domains/network.h"
+#include "headless/public/devtools/domains/page.h"
+#include "headless/public/headless_browser.h"
+#include "headless/public/headless_devtools_client.h"
+#include "headless/public/headless_devtools_target.h"
+#include "headless/test/headless_browser_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace headless {
+
+namespace {
+
+std::string NormaliseJSON(const std::string& json) {
+ std::unique_ptr<base::Value> parsed_json = base::JSONReader::Read(json);
+ DCHECK(parsed_json);
+ std::string normalized_json;
+ base::JSONWriter::WriteWithOptions(
+ *parsed_json, base::JSONWriter::OPTIONS_PRETTY_PRINT, &normalized_json);
+ return normalized_json;
+}
+
+} // namespace
+
+class FlatDomTreeExtractorBrowserTest
+ : public HeadlessAsyncDevTooledBrowserTest,
+ public page::Observer {
+ public:
+ void RunDevTooledTest() override {
+ EXPECT_TRUE(embedded_test_server()->Start());
+ devtools_client_->GetPage()->AddObserver(this);
+ devtools_client_->GetPage()->Enable();
+ devtools_client_->GetPage()->Navigate(
+ embedded_test_server()->GetURL("/dom_tree_test.html").spec());
+ }
+
+ void OnLoadEventFired(const page::LoadEventFiredParams& params) override {
+ devtools_client_->GetPage()->Disable();
+ devtools_client_->GetPage()->RemoveObserver(this);
+
+ extractor_.reset(new FlatDomTreeExtractor(devtools_client_.get()));
+
+ std::vector<std::string> css_whitelist = {
+ "color", "display", "font-style", "font-family",
+ "margin-left", "margin-right", "margin-top", "margin-bottom"};
+ extractor_->ExtractDomTree(
+ css_whitelist,
+ base::Bind(&FlatDomTreeExtractorBrowserTest::OnDomTreeExtracted,
+ base::Unretained(this)));
+ }
+
+ void OnDomTreeExtracted(FlatDomTreeExtractor::DomTree dom_tree) {
+ GURL::Replacements replace_port;
+ replace_port.SetPortStr("");
+
+ std::vector<std::unique_ptr<base::DictionaryValue>> dom_nodes(
+ dom_tree.dom_nodes_.size());
+
+ std::map<int, std::unique_ptr<base::ListValue>> child_lists;
+
+ // For convenience flatten the dom tree into an array.
+ for (size_t i = 0; i < dom_tree.dom_nodes_.size(); i++) {
+ dom::Node* node = const_cast<dom::Node*>(dom_tree.dom_nodes_[i]);
+
+ dom_nodes[i].reset(
+ static_cast<base::DictionaryValue*>(node->Serialize().release()));
+
+ if (node->HasParentId()) {
+ if (child_lists.find(node->GetParentId()) == child_lists.end()) {
+ child_lists.insert(std::make_pair(
+ node->GetParentId(), base::MakeUnique<base::ListValue>()));
+ }
+ child_lists[node->GetParentId()]->AppendInteger(i);
+ }
+ dom_nodes[i]->Remove("children", nullptr);
+
+ // Convert content document pointers into indexes.
+ if (node->HasContentDocument()) {
+ dom_nodes[i]->SetInteger(
+ "contentDocumentIndex",
+ dom_tree
+ .node_id_to_index_[node->GetContentDocument()->GetNodeId()]);
+ dom_nodes[i]->Remove("contentDocument", nullptr);
+ }
+
+ dom_nodes[i]->Remove("childNodeCount", nullptr);
+
+ // Frame IDs are random.
+ if (dom_nodes[i]->HasKey("frameId"))
+ dom_nodes[i]->SetString("frameId", "?");
+
+ // Ports are random.
+ std::string url;
+ if (dom_nodes[i]->GetString("baseURL", &url)) {
+ dom_nodes[i]->SetString(
+ "baseURL", GURL(url).ReplaceComponents(replace_port).spec());
+ }
+
+ if (dom_nodes[i]->GetString("documentURL", &url)) {
+ dom_nodes[i]->SetString(
+ "documentURL", GURL(url).ReplaceComponents(replace_port).spec());
+ }
+ }
+
+ for (auto& pair : child_lists) {
+ dom_nodes[dom_tree.node_id_to_index_[pair.first]]->Set(
+ "childIndices", std::move(pair.second));
+ }
+
+ // Merge LayoutTreeNode data into the dictionaries.
+ for (const css::LayoutTreeNode* layout_node : dom_tree.layout_tree_nodes_) {
+ auto it = dom_tree.node_id_to_index_.find(layout_node->GetNodeId());
+ ASSERT_TRUE(it != dom_tree.node_id_to_index_.end());
+
+ base::DictionaryValue* node_dict = dom_nodes[it->second].get();
+ node_dict->Set("boundingBox", layout_node->GetBoundingBox()->Serialize());
+
+ if (layout_node->HasLayoutText())
+ node_dict->SetString("layoutText", layout_node->GetLayoutText());
+
+ if (layout_node->HasStyleIndex())
+ node_dict->SetInteger("styleIndex", layout_node->GetStyleIndex());
+
+ if (layout_node->HasInlineTextNodes()) {
+ std::unique_ptr<base::ListValue> inline_text_nodes(
+ new base::ListValue());
+ for (const std::unique_ptr<css::InlineTextBox>& inline_text_box :
+ *layout_node->GetInlineTextNodes()) {
+ size_t index = inline_text_nodes->GetSize();
+ inline_text_nodes->Set(index, inline_text_box->Serialize());
+ }
+ node_dict->Set("inlineTextNodes", std::move(inline_text_nodes));
+ }
+ }
+
+ std::vector<std::unique_ptr<base::DictionaryValue>> computed_styles(
+ dom_tree.computed_styles_.size());
+
+ for (size_t i = 0; i < dom_tree.computed_styles_.size(); i++) {
+ std::unique_ptr<base::DictionaryValue> style(new base::DictionaryValue());
+ for (const auto& style_property :
+ *dom_tree.computed_styles_[i]->GetProperties()) {
+ style->SetString(style_property->GetName(), style_property->GetValue());
+ }
+ computed_styles[i] = std::move(style);
+ }
+
+ const std::vector<std::string> expected_dom_nodes = {
+ R"raw_string({
+ "backendNodeId": 7,
+ "localName": "",
+ "nodeId": 5,
+ "nodeName": "#text",
+ "nodeType": 3,
+ "nodeValue": "Hello world!",
+ "parentId": 4
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ ],
+ "backendNodeId": 6,
+ "childIndices": [ 0 ],
+ "localName": "title",
+ "nodeId": 4,
+ "nodeName": "TITLE",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 3
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ "href", "dom_tree_test.css", "rel", "stylesheet",
+ "type", "text/css" ],
+ "backendNodeId": 8,
+ "localName": "link",
+ "nodeId": 6,
+ "nodeName": "LINK",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 3
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ ],
+ "backendNodeId": 5,
+ "childIndices": [ 1, 2 ],
+ "localName": "head",
+ "nodeId": 3,
+ "nodeName": "HEAD",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 2
+ })raw_string",
+
+ R"raw_string({
+ "backendNodeId": 12,
+ "boundingBox": {
+ "height": 32.0,
+ "width": 320.0,
+ "x": 8.0,
+ "y": 8.0
+ },
+ "inlineTextNodes": [ {
+ "boundingBox": {
+ "height": 32.0,
+ "width": 320.0,
+ "x": 8.0,
+ "y": 8.0
+ },
+ "numCharacters": 10,
+ "startCharacterIndex": 0
+ } ],
+ "layoutText": "Some text.",
+ "localName": "",
+ "nodeId": 10,
+ "nodeName": "#text",
+ "nodeType": 3,
+ "nodeValue": "Some text.",
+ "parentId": 9,
+ "styleIndex": 2
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ "class", "red" ],
+ "backendNodeId": 11,
+ "boundingBox": {
+ "height": 32.0,
+ "width": 784.0,
+ "x": 8.0,
+ "y": 8.0
+ },
+ "childIndices": [ 4 ],
+ "localName": "h1",
+ "nodeId": 9,
+ "nodeName": "H1",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 8,
+ "styleIndex": 2
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ ],
+ "backendNodeId": 16,
+ "localName": "head",
+ "nodeId": 14,
+ "nodeName": "HEAD",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 13
+ })raw_string",
+
+ R"raw_string({
+ "backendNodeId": 19,
+ "boundingBox": {
+ "height": 36.0,
+ "width": 308.0,
+ "x": 8.0,
+ "y": 8.0
+ },
+ "inlineTextNodes": [ {
+ "boundingBox": {
+ "height": 36.0,
+ "width": 307.734375,
+ "x": 8.0,
+ "y": 8.0
+ },
+ "numCharacters": 22,
+ "startCharacterIndex": 0
+ } ],
+ "layoutText": "Hello from the iframe!",
+ "localName": "",
+ "nodeId": 17,
+ "nodeName": "#text",
+ "nodeType": 3,
+ "nodeValue": "Hello from the iframe!",
+ "parentId": 16,
+ "styleIndex": 5
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ ],
+ "backendNodeId": 18,
+ "boundingBox": {
+ "height": 37.0,
+ "width": 384.0,
+ "x": 18.0,
+ "y": 71.0
+ },
+ "childIndices": [ 7 ],
+ "localName": "h1",
+ "nodeId": 16,
+ "nodeName": "H1",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 15,
+ "styleIndex": 5
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ ],
+ "backendNodeId": 17,
+ "boundingBox": {
+ "height": 171.0,
+ "width": 384.0,
+ "x": 18.0,
+ "y": 71.0
+ },
+ "childIndices": [ 8 ],
+ "localName": "body",
+ "nodeId": 15,
+ "nodeName": "BODY",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 13,
+ "styleIndex": 4
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ ],
+ "backendNodeId": 15,
+ "boundingBox": {
+ "height": 200.0,
+ "width": 400.0,
+ "x": 10.0,
+ "y": 63.0
+ },
+ "childIndices": [ 6, 9 ],
+ "frameId": "?",
+ "localName": "html",
+ "nodeId": 13,
+ "nodeName": "HTML",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 12,
+ "styleIndex": 3
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ "src", "/iframe.html", "width", "400", "height",
+ "200" ],
+ "backendNodeId": 13,
+ "boundingBox": {
+ "height": 205.0,
+ "width": 404.0,
+ "x": 8.0,
+ "y": 61.0
+ },
+ "contentDocumentIndex": 12,
+ "frameId": "?",
+ "localName": "iframe",
+ "nodeId": 11,
+ "nodeName": "IFRAME",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 8,
+ "styleIndex": 6
+ })raw_string",
+
+ R"raw_string({
+ "backendNodeId": 14,
+ "baseURL": "http://127.0.0.1/iframe.html",
+ "childIndices": [ 10 ],
+ "documentURL": "http://127.0.0.1/iframe.html",
+ "localName": "",
+ "nodeId": 12,
+ "nodeName": "#document",
+ "nodeType": 9,
+ "nodeValue": "",
+ "xmlVersion": ""
+ })raw_string",
+
+ R"raw_string({
+ "backendNodeId": 24,
+ "boundingBox": {
+ "height": 17.0,
+ "width": 112.0,
+ "x": 8.0,
+ "y": 265.0
+ },
+ "inlineTextNodes": [ {
+ "boundingBox": {
+ "height": 16.0,
+ "width": 112.0,
+ "x": 8.0,
+ "y": 265.4375
+ },
+ "numCharacters": 7,
+ "startCharacterIndex": 0
+ } ],
+ "layoutText": "Google!",
+ "localName": "",
+ "nodeId": 22,
+ "nodeName": "#text",
+ "nodeType": 3,
+ "nodeValue": "Google!",
+ "parentId": 21,
+ "styleIndex": 7
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ "href", "https://www.google.com" ],
+ "backendNodeId": 23,
+ "boundingBox": {
+ "height": 17.0,
+ "width": 112.0,
+ "x": 8.0,
+ "y": 265.0
+ },
+ "childIndices": [ 13 ],
+ "localName": "a",
+ "nodeId": 21,
+ "nodeName": "A",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 20,
+ "styleIndex": 7
+ })raw_string",
+
+ R"raw_string({
+ "backendNodeId": 26,
+ "boundingBox": {
+ "height": 17.0,
+ "width": 192.0,
+ "x": 8.0,
+ "y": 297.0
+ },
+ "inlineTextNodes": [ {
+ "boundingBox": {
+ "height": 16.0,
+ "width": 192.0,
+ "x": 8.0,
+ "y": 297.4375
+ },
+ "numCharacters": 12,
+ "startCharacterIndex": 0
+ } ],
+ "layoutText": "A paragraph!",
+ "localName": "",
+ "nodeId": 24,
+ "nodeName": "#text",
+ "nodeType": 3,
+ "nodeValue": "A paragraph!",
+ "parentId": 23,
+ "styleIndex": 8
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ ],
+ "backendNodeId": 25,
+ "boundingBox": {
+ "height": 17.0,
+ "width": 784.0,
+ "x": 8.0,
+ "y": 297.0
+ },
+ "childIndices": [ 15 ],
+ "localName": "p",
+ "nodeId": 23,
+ "nodeName": "P",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 20,
+ "styleIndex": 8
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ ],
+ "backendNodeId": 27,
+ "boundingBox": {
+ "height": 0.0,
+ "width": 0.0,
+ "x": 0.0,
+ "y": 0.0
+ },
+ "inlineTextNodes": [ {
+ "boundingBox": {
+ "height": 16.0,
+ "width": 0.0,
+ "x": 8.0,
+ "y": 329.4375
+ },
+ "numCharacters": 1,
+ "startCharacterIndex": 0
+ } ],
+ "layoutText": "\n",
+ "localName": "br",
+ "nodeId": 25,
+ "nodeName": "BR",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 20,
+ "styleIndex": 6
+ })raw_string",
+
+ R"raw_string({
+ "backendNodeId": 29,
+ "boundingBox": {
+ "height": 17.0,
+ "width": 80.0,
+ "x": 8.0,
+ "y": 345.0
+ },
+ "inlineTextNodes": [ {
+ "boundingBox": {
+ "height": 16.0,
+ "width": 80.0,
+ "x": 8.0,
+ "y": 345.4375
+ },
+ "numCharacters": 5,
+ "startCharacterIndex": 0
+ } ],
+ "layoutText": "Some ",
+ "localName": "",
+ "nodeId": 27,
+ "nodeName": "#text",
+ "nodeType": 3,
+ "nodeValue": "Some ",
+ "parentId": 26,
+ "styleIndex": 9
+ })raw_string",
+
+ R"raw_string({
+ "backendNodeId": 31,
+ "boundingBox": {
+ "height": 17.0,
+ "width": 80.0,
+ "x": 88.0,
+ "y": 345.0
+ },
+ "inlineTextNodes": [ {
+ "boundingBox": {
+ "height": 16.0,
+ "width": 80.0,
+ "x": 88.0,
+ "y": 345.4375
+ },
+ "numCharacters": 5,
+ "startCharacterIndex": 0
+ } ],
+ "layoutText": "green",
+ "localName": "",
+ "nodeId": 29,
+ "nodeName": "#text",
+ "nodeType": 3,
+ "nodeValue": "green",
+ "parentId": 28,
+ "styleIndex": 10
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ ],
+ "backendNodeId": 30,
+ "boundingBox": {
+ "height": 17.0,
+ "width": 80.0,
+ "x": 88.0,
+ "y": 345.0
+ },
+ "childIndices": [ 19 ],
+ "localName": "em",
+ "nodeId": 28,
+ "nodeName": "EM",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 26,
+ "styleIndex": 10
+ })raw_string",
+
+ R"raw_string({
+ "backendNodeId": 32,
+ "boundingBox": {
+ "height": 17.0,
+ "width": 128.0,
+ "x": 168.0,
+ "y": 345.0
+ },
+ "inlineTextNodes": [ {
+ "boundingBox": {
+ "height": 16.0,
+ "width": 128.0,
+ "x": 168.0,
+ "y": 345.4375
+ },
+ "numCharacters": 8,
+ "startCharacterIndex": 0
+ } ],
+ "layoutText": " text...",
+ "localName": "",
+ "nodeId": 30,
+ "nodeName": "#text",
+ "nodeType": 3,
+ "nodeValue": " text...",
+ "parentId": 26,
+ "styleIndex": 9
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ "class", "green" ],
+ "backendNodeId": 28,
+ "boundingBox": {
+ "height": 17.0,
+ "width": 784.0,
+ "x": 8.0,
+ "y": 345.0
+ },
+ "childIndices": [ 18, 20, 21 ],
+ "localName": "div",
+ "nodeId": 26,
+ "nodeName": "DIV",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 20,
+ "styleIndex": 9
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ "id", "id4" ],
+ "backendNodeId": 22,
+ "boundingBox": {
+ "height": 97.0,
+ "width": 784.0,
+ "x": 8.0,
+ "y": 265.0
+ },
+ "childIndices": [ 14, 16, 17, 22 ],
+ "localName": "div",
+ "nodeId": 20,
+ "nodeName": "DIV",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 19,
+ "styleIndex": 0
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ "id", "id3" ],
+ "backendNodeId": 21,
+ "boundingBox": {
+ "height": 97.0,
+ "width": 784.0,
+ "x": 8.0,
+ "y": 265.0
+ },
+ "childIndices": [ 23 ],
+ "localName": "div",
+ "nodeId": 19,
+ "nodeName": "DIV",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 18,
+ "styleIndex": 0
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ "id", "id2" ],
+ "backendNodeId": 20,
+ "boundingBox": {
+ "height": 97.0,
+ "width": 784.0,
+ "x": 8.0,
+ "y": 265.0
+ },
+ "childIndices": [ 24 ],
+ "localName": "div",
+ "nodeId": 18,
+ "nodeName": "DIV",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 8,
+ "styleIndex": 0
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ "id", "id1" ],
+ "backendNodeId": 10,
+ "boundingBox": {
+ "height": 354.0,
+ "width": 784.0,
+ "x": 8.0,
+ "y": 8.0
+ },
+ "childIndices": [ 5, 11, 25 ],
+ "localName": "div",
+ "nodeId": 8,
+ "nodeName": "DIV",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 7,
+ "styleIndex": 0
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ ],
+ "backendNodeId": 9,
+ "boundingBox": {
+ "height": 584.0,
+ "width": 784.0,
+ "x": 8.0,
+ "y": 8.0
+ },
+ "childIndices": [ 26 ],
+ "localName": "body",
+ "nodeId": 7,
+ "nodeName": "BODY",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 2,
+ "styleIndex": 1
+ })raw_string",
+
+ R"raw_string({
+ "attributes": [ ],
+ "backendNodeId": 4,
+ "boundingBox": {
+ "height": 600.0,
+ "width": 800.0,
+ "x": 0.0,
+ "y": 0.0
+ },
+ "childIndices": [ 3, 27 ],
+ "frameId": "?",
+ "localName": "html",
+ "nodeId": 2,
+ "nodeName": "HTML",
+ "nodeType": 1,
+ "nodeValue": "",
+ "parentId": 1,
+ "styleIndex": 0
+ })raw_string",
+
+ R"raw_string({
+ "backendNodeId": 3,
+ "baseURL": "http://127.0.0.1/dom_tree_test.html",
+ "boundingBox": {
+ "height": 600.0,
+ "width": 800.0,
+ "x": 0.0,
+ "y": 0.0
+ },
+ "childIndices": [ 28 ],
+ "documentURL": "http://127.0.0.1/dom_tree_test.html",
+ "localName": "",
+ "nodeId": 1,
+ "nodeName": "#document",
+ "nodeType": 9,
+ "nodeValue": "",
+ "xmlVersion": ""
+ })raw_string"};
+
+ EXPECT_EQ(expected_dom_nodes.size(), dom_nodes.size());
+
+ for (size_t i = 0; i < dom_nodes.size(); i++) {
+ std::string result_json;
+ base::JSONWriter::WriteWithOptions(
+ *dom_nodes[i], base::JSONWriter::OPTIONS_PRETTY_PRINT, &result_json);
+
+ ASSERT_LT(i, expected_dom_nodes.size());
+ EXPECT_EQ(NormaliseJSON(expected_dom_nodes[i]), result_json) << " Node # "
+ << i;
+ }
+
+ const std::vector<std::string> expected_styles = {
+ R"raw_string({
+ "color": "rgb(0, 0, 0)",
+ "display": "block",
+ "font-family": "ahem",
+ "font-style": "normal",
+ "margin-bottom": "0px",
+ "margin-left": "0px",
+ "margin-right": "0px",
+ "margin-top": "0px"
+ })raw_string",
+
+ R"raw_string({
+ "color": "rgb(0, 0, 0)",
+ "display": "block",
+ "font-family": "ahem",
+ "font-style": "normal",
+ "margin-bottom": "8px",
+ "margin-left": "8px",
+ "margin-right": "8px",
+ "margin-top": "8px"
+ })raw_string",
+
+ R"raw_string({
+ "color": "rgb(255, 0, 0)",
+ "display": "block",
+ "font-family": "ahem",
+ "font-style": "normal",
+ "margin-bottom": "21.44px",
+ "margin-left": "0px",
+ "margin-right": "0px",
+ "margin-top": "21.44px"
+ })raw_string",
+
+ R"raw_string({
+ "color": "rgb(0, 0, 0)",
+ "display": "block",
+ "font-family": "\"Times New Roman\"",
+ "font-style": "normal",
+ "margin-bottom": "0px",
+ "margin-left": "0px",
+ "margin-right": "0px",
+ "margin-top": "0px"
+ })raw_string",
+
+ R"raw_string({
+ "color": "rgb(0, 0, 0)",
+ "display": "block",
+ "font-family": "\"Times New Roman\"",
+ "font-style": "normal",
+ "margin-bottom": "8px",
+ "margin-left": "8px",
+ "margin-right": "8px",
+ "margin-top": "8px"
+ })raw_string",
+
+ R"raw_string({
+ "color": "rgb(0, 0, 0)",
+ "display": "block",
+ "font-family": "\"Times New Roman\"",
+ "font-style": "normal",
+ "margin-bottom": "21.44px",
+ "margin-left": "0px",
+ "margin-right": "0px",
+ "margin-top": "21.44px"
+ })raw_string",
+
+ R"raw_string({
+ "color": "rgb(0, 0, 0)",
+ "display": "inline",
+ "font-family": "ahem",
+ "font-style": "normal",
+ "margin-bottom": "0px",
+ "margin-left": "0px",
+ "margin-right": "0px",
+ "margin-top": "0px"
+ })raw_string",
+
+ R"raw_string({
+ "color": "rgb(0, 0, 238)",
+ "display": "inline",
+ "font-family": "ahem",
+ "font-style": "normal",
+ "margin-bottom": "0px",
+ "margin-left": "0px",
+ "margin-right": "0px",
+ "margin-top": "0px"
+ })raw_string",
+
+ R"raw_string({
+ "color": "rgb(0, 0, 0)",
+ "display": "block",
+ "font-family": "ahem",
+ "font-style": "normal",
+ "margin-bottom": "16px",
+ "margin-left": "0px",
+ "margin-right": "0px",
+ "margin-top": "16px"
+ })raw_string",
+
+ R"raw_string({
+ "color": "rgb(0, 128, 0)",
+ "display": "block",
+ "font-family": "ahem",
+ "font-style": "normal",
+ "margin-bottom": "0px",
+ "margin-left": "0px",
+ "margin-right": "0px",
+ "margin-top": "0px"
+ })raw_string",
+
+ R"raw_string({
+ "color": "rgb(0, 128, 0)",
+ "display": "inline",
+ "font-family": "ahem",
+ "font-style": "italic",
+ "margin-bottom": "0px",
+ "margin-left": "0px",
+ "margin-right": "0px",
+ "margin-top": "0px"
+ }
+ )raw_string"};
+
+ for (size_t i = 0; i < computed_styles.size(); i++) {
+ std::string result_json;
+ base::JSONWriter::WriteWithOptions(*computed_styles[i],
+ base::JSONWriter::OPTIONS_PRETTY_PRINT,
+ &result_json);
+
+ ASSERT_LT(i, expected_styles.size());
+ EXPECT_EQ(NormaliseJSON(expected_styles[i]), result_json) << " Style # "
+ << i;
+ }
+
+ FinishAsynchronousTest();
+ }
+
+ std::unique_ptr<FlatDomTreeExtractor> extractor_;
+};
+
+HEADLESS_ASYNC_DEVTOOLED_TEST_F(FlatDomTreeExtractorBrowserTest);
+
+} // namespace headless
diff --git a/chromium/headless/public/util/generic_url_request_job.cc b/chromium/headless/public/util/generic_url_request_job.cc
index a82925d9623..7918dec7836 100644
--- a/chromium/headless/public/util/generic_url_request_job.cc
+++ b/chromium/headless/public/util/generic_url_request_job.cc
@@ -25,6 +25,9 @@ bool IsMethodSafe(const std::string& method) {
method == "TRACE";
}
+// Keep in sync with X_DevTools_Request_Id defined in HTTPNames.json5.
+const char kDevtoolsRequestId[] = "X-DevTools-Request-Id";
+
} // namespace
GenericURLRequestJob::GenericURLRequestJob(
@@ -45,6 +48,11 @@ GenericURLRequestJob::~GenericURLRequestJob() = default;
void GenericURLRequestJob::SetExtraRequestHeaders(
const net::HttpRequestHeaders& headers) {
extra_request_headers_ = headers;
+
+ if (extra_request_headers_.GetHeader(kDevtoolsRequestId,
+ &devtools_request_id_)) {
+ extra_request_headers_.RemoveHeader(kDevtoolsRequestId);
+ }
}
void GenericURLRequestJob::Start() {
@@ -67,7 +75,8 @@ void GenericURLRequestJob::Start() {
}
};
- if (!delegate_->BlockOrRewriteRequest(request_->url(), request_->method(),
+ if (!delegate_->BlockOrRewriteRequest(request_->url(), devtools_request_id_,
+ request_->method(),
request_->referrer(), callback)) {
PrepareCookies(request_->url(), request_->method(),
url::Origin(request_->first_party_for_cookies()));
@@ -118,7 +127,7 @@ void GenericURLRequestJob::OnCookiesAvailable(
// The resource may have been supplied in the request.
const HttpResponse* matched_resource = delegate_->MaybeMatchResource(
- rewritten_url, method, extra_request_headers_);
+ rewritten_url, devtools_request_id_, method, extra_request_headers_);
if (matched_resource) {
OnFetchCompleteExtractHeaders(
@@ -151,7 +160,8 @@ void GenericURLRequestJob::OnFetchComplete(
std::string mime_type;
GetMimeType(&mime_type);
- delegate_->OnResourceLoadComplete(final_url, mime_type, http_response_code);
+ delegate_->OnResourceLoadComplete(final_url, devtools_request_id_, mime_type,
+ http_response_code);
}
int GenericURLRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size) {
@@ -193,4 +203,46 @@ void GenericURLRequestJob::GetLoadTimingInfo(
load_timing_info->receive_headers_end = response_time_;
}
+bool GenericURLRequestJob::Delegate::BlockOrRewriteRequest(
+ const GURL& url,
+ const std::string& devtools_id,
+ const std::string& method,
+ const std::string& referrer,
+ RewriteCallback callback) {
+ return BlockOrRewriteRequest(url, method, referrer, callback);
+}
+
+bool GenericURLRequestJob::Delegate::BlockOrRewriteRequest(
+ const GURL& url,
+ const std::string& method,
+ const std::string& referrer,
+ RewriteCallback callback) {
+ return false;
+}
+
+const GenericURLRequestJob::HttpResponse*
+GenericURLRequestJob::Delegate::MaybeMatchResource(
+ const GURL& url,
+ const std::string& devtools_id,
+ const std::string& method,
+ const net::HttpRequestHeaders& request_headers) {
+ return MaybeMatchResource(url, method, request_headers);
+}
+
+const GenericURLRequestJob::HttpResponse*
+GenericURLRequestJob::Delegate::MaybeMatchResource(
+ const GURL& url,
+ const std::string& method,
+ const net::HttpRequestHeaders& request_headers) {
+ return nullptr;
+}
+
+void GenericURLRequestJob::Delegate::OnResourceLoadComplete(
+ const GURL& final_url,
+ const std::string& devtools_id,
+ const std::string& mime_type,
+ int http_response_code) {
+ OnResourceLoadComplete(final_url, mime_type, http_response_code);
+}
+
} // namespace headless
diff --git a/chromium/headless/public/util/generic_url_request_job.h b/chromium/headless/public/util/generic_url_request_job.h
index 850f0ce6c99..1bb08905531 100644
--- a/chromium/headless/public/util/generic_url_request_job.h
+++ b/chromium/headless/public/util/generic_url_request_job.h
@@ -58,21 +58,38 @@ class GenericURLRequestJob : public ManagedDispatchURLRequestJob,
// with the result, or false to indicate that no rewriting is necessary.
// Called on an arbitrary thread.
virtual bool BlockOrRewriteRequest(const GURL& url,
+ const std::string& devtools_id,
const std::string& method,
const std::string& referrer,
- RewriteCallback callback) = 0;
+ RewriteCallback callback);
+ // TODO(alexclarke): Make the above pure virtual and remove this.
+ virtual bool BlockOrRewriteRequest(const GURL& url,
+ const std::string& method,
+ const std::string& referrer,
+ RewriteCallback callback);
// Allows the delegate to synchronously fulfill a request with a reply.
// Called on an arbitrary thread.
virtual const HttpResponse* MaybeMatchResource(
const GURL& url,
+ const std::string& devtools_id,
+ const std::string& method,
+ const net::HttpRequestHeaders& request_headers);
+ // TODO(alexclarke): Make the above pure virtual and remove this.
+ virtual const HttpResponse* MaybeMatchResource(
+ const GURL& url,
const std::string& method,
- const net::HttpRequestHeaders& request_headers) = 0;
+ const net::HttpRequestHeaders& request_headers);
// Signals that a resource load has finished. Called on an arbitrary thread.
virtual void OnResourceLoadComplete(const GURL& final_url,
+ const std::string& devtools_id,
+ const std::string& mime_type,
+ int http_response_code);
+ // TODO(alexclarke): Make the above pure virtual and remove this.
+ virtual void OnResourceLoadComplete(const GURL& final_url,
const std::string& mime_type,
- int http_response_code) = 0;
+ int http_response_code) {}
protected:
virtual ~Delegate() {}
@@ -117,6 +134,7 @@ class GenericURLRequestJob : public ManagedDispatchURLRequestJob,
std::unique_ptr<URLFetcher> url_fetcher_;
net::HttpRequestHeaders extra_request_headers_;
scoped_refptr<net::HttpResponseHeaders> response_headers_;
+ std::string devtools_request_id_;
Delegate* delegate_; // Not owned.
const char* body_ = nullptr; // Not owned.
int http_response_code_ = 0;
diff --git a/chromium/headless/public/util/generic_url_request_job_test.cc b/chromium/headless/public/util/generic_url_request_job_test.cc
index e1942f86e8b..79dc9e2bf52 100644
--- a/chromium/headless/public/util/generic_url_request_job_test.cc
+++ b/chromium/headless/public/util/generic_url_request_job_test.cc
@@ -281,7 +281,7 @@ TEST_F(GenericURLRequestJobTest, RequestWithCookies) {
base::Time(), base::Time(),
/* secure */ false,
/* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
- /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
+ net::COOKIE_PRIORITY_DEFAULT));
// Matching secure cookie.
cookies->push_back(*net::CanonicalCookie::Create(
@@ -289,7 +289,7 @@ TEST_F(GenericURLRequestJobTest, RequestWithCookies) {
base::Time(), base::Time(),
/* secure */ true,
/* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
- /* enforce_strict_secure */ true, net::COOKIE_PRIORITY_DEFAULT));
+ net::COOKIE_PRIORITY_DEFAULT));
// Matching http-only cookie.
cookies->push_back(*net::CanonicalCookie::Create(
@@ -297,7 +297,7 @@ TEST_F(GenericURLRequestJobTest, RequestWithCookies) {
base::Time(), base::Time(),
/* secure */ false,
/* http_only */ true, net::CookieSameSite::NO_RESTRICTION,
- /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
+ net::COOKIE_PRIORITY_DEFAULT));
// Matching cookie with path.
cookies->push_back(*net::CanonicalCookie::Create(
@@ -305,7 +305,7 @@ TEST_F(GenericURLRequestJobTest, RequestWithCookies) {
"/widgets", base::Time(), base::Time(),
/* secure */ false,
/* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
- /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
+ net::COOKIE_PRIORITY_DEFAULT));
// Matching cookie with subdomain.
cookies->push_back(*net::CanonicalCookie::Create(
@@ -313,7 +313,7 @@ TEST_F(GenericURLRequestJobTest, RequestWithCookies) {
"cdn.example.com", "/", base::Time(), base::Time(),
/* secure */ false,
/* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
- /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
+ net::COOKIE_PRIORITY_DEFAULT));
// Non-matching cookie (different site).
cookies->push_back(*net::CanonicalCookie::Create(
@@ -321,7 +321,7 @@ TEST_F(GenericURLRequestJobTest, RequestWithCookies) {
base::Time(), base::Time(),
/* secure */ false,
/* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
- /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
+ net::COOKIE_PRIORITY_DEFAULT));
// Non-matching cookie (different path).
cookies->push_back(*net::CanonicalCookie::Create(
@@ -329,7 +329,7 @@ TEST_F(GenericURLRequestJobTest, RequestWithCookies) {
"/gadgets", base::Time(), base::Time(),
/* secure */ false,
/* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
- /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
+ net::COOKIE_PRIORITY_DEFAULT));
std::string reply =
"{\"url\":\"https://example.com\","
diff --git a/chromium/headless/public/util/navigation_request.h b/chromium/headless/public/util/navigation_request.h
new file mode 100644
index 00000000000..cd28f10dd93
--- /dev/null
+++ b/chromium/headless/public/util/navigation_request.h
@@ -0,0 +1,31 @@
+// Copyright 2017 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_UTIL_NAVIGATION_REQUEST_H_
+#define HEADLESS_PUBLIC_UTIL_NAVIGATION_REQUEST_H_
+
+#include "base/macros.h"
+
+namespace headless {
+
+// While the actual details of the navigation processing are left undefined,
+// it's anticipated implementations will use devtools Page.setControlNavigations
+// and Page.processNavigation commands.
+class NavigationRequest {
+ public:
+ NavigationRequest() {}
+ virtual ~NavigationRequest() {}
+
+ // Called on the IO thread to ask the implementation to start processing the
+ // navigation request. The NavigationRequest will be deleted immediately after
+ // The |done_callback| can be called from any thread.
+ virtual void StartProcessing(base::Closure done_callback) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NavigationRequest);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_NAVIGATION_REQUEST_H_
diff --git a/chromium/headless/public/util/protocol_handler_request_id_browsertest.cc b/chromium/headless/public/util/protocol_handler_request_id_browsertest.cc
new file mode 100644
index 00000000000..986bf1f1298
--- /dev/null
+++ b/chromium/headless/public/util/protocol_handler_request_id_browsertest.cc
@@ -0,0 +1,218 @@
+// Copyright 2017 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/devtools/domains/network.h"
+#include "headless/public/devtools/domains/page.h"
+#include "headless/public/headless_devtools_client.h"
+#include "headless/public/util/expedited_dispatcher.h"
+#include "headless/public/util/generic_url_request_job.h"
+#include "headless/public/util/url_fetcher.h"
+#include "headless/test/headless_browser_test.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using testing::ContainerEq;
+
+namespace headless {
+
+namespace {
+// Keep in sync with X_DevTools_Request_Id defined in HTTPNames.json5.
+const char kDevtoolsRequestId[] = "X-DevTools-Request-Id";
+} // namespace
+
+namespace {
+class RequestIdCorrelationProtocolHandler
+ : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ explicit RequestIdCorrelationProtocolHandler(
+ scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner)
+ : test_delegate_(new TestDelegate(this)),
+ dispatcher_(new ExpeditedDispatcher(io_thread_task_runner)) {}
+
+ ~RequestIdCorrelationProtocolHandler() override {}
+
+ struct Response {
+ Response() {}
+ Response(const std::string& body, const std::string& mime_type)
+ : data("HTTP/1.1 200 OK\r\nContent-Type: " + mime_type + "\r\n\r\n" +
+ body) {}
+
+ std::string data;
+ };
+
+ void InsertResponse(const std::string& url, const Response& response) {
+ response_map_[url] = response;
+ }
+
+ const Response* GetResponse(const std::string& url) const {
+ std::map<std::string, Response>::const_iterator find_it =
+ response_map_.find(url);
+ if (find_it == response_map_.end())
+ return nullptr;
+ return &find_it->second;
+ }
+
+ class MockURLFetcher : public URLFetcher {
+ public:
+ explicit MockURLFetcher(
+ const RequestIdCorrelationProtocolHandler* protocol_handler)
+ : protocol_handler_(protocol_handler) {}
+ ~MockURLFetcher() override {}
+
+ // URLFetcher implementation:
+ void StartFetch(const GURL& url,
+ const std::string& method,
+ const net::HttpRequestHeaders& request_headers,
+ ResultListener* result_listener) override {
+ const Response* response = protocol_handler_->GetResponse(url.spec());
+ if (!response)
+ result_listener->OnFetchStartError(net::ERR_FILE_NOT_FOUND);
+
+ // The header used for correlation should not be sent to the fetcher.
+ EXPECT_FALSE(request_headers.HasHeader(kDevtoolsRequestId));
+
+ result_listener->OnFetchCompleteExtractHeaders(
+ url, 200, response->data.c_str(), response->data.size());
+ }
+
+ private:
+ const RequestIdCorrelationProtocolHandler* protocol_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockURLFetcher);
+ };
+
+ class TestDelegate : public GenericURLRequestJob::Delegate {
+ public:
+ explicit TestDelegate(RequestIdCorrelationProtocolHandler* protocol_handler)
+ : protocol_handler_(protocol_handler) {}
+ ~TestDelegate() override {}
+
+ // GenericURLRequestJob::Delegate implementation:
+ bool BlockOrRewriteRequest(
+ const GURL& url,
+ const std::string& devtools_id,
+ const std::string& method,
+ const std::string& referrer,
+ GenericURLRequestJob::RewriteCallback callback) override {
+ protocol_handler_->url_to_devtools_id_[url.spec()] = devtools_id;
+ return false;
+ }
+
+ const GenericURLRequestJob::HttpResponse* MaybeMatchResource(
+ const GURL& url,
+ const std::string& devtools_id,
+ const std::string& method,
+ const net::HttpRequestHeaders& request_headers) override {
+ return nullptr;
+ }
+
+ void OnResourceLoadComplete(const GURL& final_url,
+ const std::string& devtools_id,
+ const std::string& mime_type,
+ int http_response_code) override {}
+
+ private:
+ RequestIdCorrelationProtocolHandler* protocol_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestDelegate);
+ };
+
+ // net::URLRequestJobFactory::ProtocolHandler implementation::
+ net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override {
+ return new GenericURLRequestJob(
+ request, network_delegate, dispatcher_.get(),
+ base::MakeUnique<MockURLFetcher>(this), test_delegate_.get());
+ }
+
+ std::map<std::string, std::string> url_to_devtools_id_;
+
+ private:
+ std::unique_ptr<TestDelegate> test_delegate_;
+ std::unique_ptr<ExpeditedDispatcher> dispatcher_;
+ std::map<std::string, Response> response_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(RequestIdCorrelationProtocolHandler);
+};
+
+const char* kIndexHtml = R"(
+<html>
+<head>
+<link rel="stylesheet" type="text/css" href="style1.css">
+<link rel="stylesheet" type="text/css" href="style2.css">
+</head>
+<body>Hello.
+</body>
+</html>)";
+
+const char* kStyle1 = R"(
+.border {
+ border: 1px solid #000;
+})";
+
+const char* kStyle2 = R"(
+.border {
+ border: 2px solid #fff;
+})";
+
+} // namespace
+
+class ProtocolHandlerRequestIdCorrelationTest
+ : public HeadlessAsyncDevTooledBrowserTest,
+ public network::Observer,
+ public page::Observer {
+ public:
+ void RunDevTooledTest() override {
+ EXPECT_TRUE(embedded_test_server()->Start());
+ devtools_client_->GetPage()->AddObserver(this);
+ devtools_client_->GetPage()->Enable();
+ devtools_client_->GetNetwork()->AddObserver(this);
+ devtools_client_->GetNetwork()->Enable();
+ devtools_client_->GetPage()->Navigate("http://foo.com/index.html");
+ }
+
+ ProtocolHandlerMap GetProtocolHandlers() override {
+ ProtocolHandlerMap protocol_handlers;
+ std::unique_ptr<RequestIdCorrelationProtocolHandler> http_handler(
+ new RequestIdCorrelationProtocolHandler(browser()->BrowserIOThread()));
+ http_handler_ = http_handler.get();
+ http_handler_->InsertResponse("http://foo.com/index.html",
+ {kIndexHtml, "text/html"});
+ http_handler_->InsertResponse("http://foo.com/style1.css",
+ {kStyle1, "text/css"});
+ http_handler_->InsertResponse("http://foo.com/style2.css",
+ {kStyle2, "text/css"});
+ protocol_handlers[url::kHttpScheme] = std::move(http_handler);
+ return protocol_handlers;
+ }
+
+ // network::Observer implementation:
+ void OnRequestWillBeSent(
+ const network::RequestWillBeSentParams& params) override {
+ url_to_devtools_id_[params.GetRequest()->GetUrl()] = params.GetRequestId();
+ EXPECT_FALSE(params.GetRequest()->GetHeaders()->HasKey(kDevtoolsRequestId));
+ }
+
+ // page::Observer implementation:
+ void OnLoadEventFired(const page::LoadEventFiredParams& params) override {
+ // Make sure that our protocol handler saw the same url : devtools ids as
+ // our OnRequestWillBeSent event listener did.
+ EXPECT_THAT(url_to_devtools_id_,
+ ContainerEq(http_handler_->url_to_devtools_id_));
+ EXPECT_EQ(3u, url_to_devtools_id_.size());
+ FinishAsynchronousTest();
+ }
+
+ private:
+ std::map<std::string, std::string> url_to_devtools_id_;
+ RequestIdCorrelationProtocolHandler* http_handler_; // NOT OWNED
+};
+
+HEADLESS_ASYNC_DEVTOOLED_TEST_F(ProtocolHandlerRequestIdCorrelationTest);
+
+} // namespace headless
diff --git a/chromium/headless/public/util/testing/generic_url_request_mocks.cc b/chromium/headless/public/util/testing/generic_url_request_mocks.cc
index adcabbef839..54e6c67c54a 100644
--- a/chromium/headless/public/util/testing/generic_url_request_mocks.cc
+++ b/chromium/headless/public/util/testing/generic_url_request_mocks.cc
@@ -20,6 +20,7 @@ MockGenericURLRequestJobDelegate::~MockGenericURLRequestJobDelegate() {}
bool MockGenericURLRequestJobDelegate::BlockOrRewriteRequest(
const GURL& url,
+ const std::string& devtools_id,
const std::string& method,
const std::string& referrer,
GenericURLRequestJob::RewriteCallback callback) {
@@ -31,6 +32,7 @@ bool MockGenericURLRequestJobDelegate::BlockOrRewriteRequest(
const GenericURLRequestJob::HttpResponse*
MockGenericURLRequestJobDelegate::MaybeMatchResource(
const GURL& url,
+ const std::string& devtools_id,
const std::string& method,
const net::HttpRequestHeaders& request_headers) {
return nullptr;
@@ -38,6 +40,7 @@ MockGenericURLRequestJobDelegate::MaybeMatchResource(
void MockGenericURLRequestJobDelegate::OnResourceLoadComplete(
const GURL& final_url,
+ const std::string& devtools_id,
const std::string& mime_type,
int http_response_code) {}
@@ -65,7 +68,6 @@ void MockCookieStore::SetCookieWithDetailsAsync(
bool secure,
bool http_only,
net::CookieSameSite same_site,
- bool enforce_strict_secure,
net::CookiePriority priority,
const SetCookiesCallback& callback) {
CHECK(false);
diff --git a/chromium/headless/public/util/testing/generic_url_request_mocks.h b/chromium/headless/public/util/testing/generic_url_request_mocks.h
index fb8f0bef954..8483e569139 100644
--- a/chromium/headless/public/util/testing/generic_url_request_mocks.h
+++ b/chromium/headless/public/util/testing/generic_url_request_mocks.h
@@ -25,16 +25,19 @@ class MockGenericURLRequestJobDelegate : public GenericURLRequestJob::Delegate {
bool BlockOrRewriteRequest(
const GURL& url,
+ const std::string& devtools_id,
const std::string& method,
const std::string& referrer,
GenericURLRequestJob::RewriteCallback callback) override;
const GenericURLRequestJob::HttpResponse* MaybeMatchResource(
const GURL& url,
+ const std::string& devtools_id,
const std::string& method,
const net::HttpRequestHeaders& request_headers) override;
void OnResourceLoadComplete(const GURL& final_url,
+ const std::string& devtools_id,
const std::string& mime_type,
int http_response_code) override;
@@ -69,7 +72,6 @@ class MockCookieStore : public net::CookieStore {
bool secure,
bool http_only,
net::CookieSameSite same_site,
- bool enforce_strict_secure,
net::CookiePriority priority,
const SetCookiesCallback& callback) override;
diff --git a/chromium/headless/public/util/url_request_dispatcher.h b/chromium/headless/public/util/url_request_dispatcher.h
index 1f34509a3a5..3029dd5c2b8 100644
--- a/chromium/headless/public/util/url_request_dispatcher.h
+++ b/chromium/headless/public/util/url_request_dispatcher.h
@@ -10,6 +10,7 @@
namespace headless {
class ManagedDispatchURLRequestJob;
+class NavigationRequest;
// Interface to abstract and potentially reorder (for determinism) calls to
// ManagedDispatchUrlRequestJob::OnHeadersComplete and
@@ -36,6 +37,10 @@ class URLRequestDispatcher {
// Tells us the job has finished. Can be called from any thread.
virtual void JobDeleted(ManagedDispatchURLRequestJob* job) = 0;
+ // Tells us a navigation has been requested. Can be called from any thread.
+ virtual void NavigationRequested(
+ std::unique_ptr<NavigationRequest> navigation_request) = 0;
+
private:
DISALLOW_COPY_AND_ASSIGN(URLRequestDispatcher);
};
diff --git a/chromium/headless/public/version.h.in b/chromium/headless/public/version.h.in
new file mode 100644
index 00000000000..7e54cb131cd
--- /dev/null
+++ b/chromium/headless/public/version.h.in
@@ -0,0 +1,13 @@
+// Copyright 2017 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.
+
+// version.h is generated from version.h.in. Edit the source!
+
+#ifndef HEADLESS_PUBLIC_VERSION_H_
+#define HEADLESS_PUBLIC_VERSION_H_
+
+#define PRODUCT_VERSION "@MAJOR@.@MINOR@.@BUILD@.@PATCH@"
+#define LAST_CHANGE "@LASTCHANGE@"
+
+#endif // HEADLESS_PUBLIC_VERSION_H_