summaryrefslogtreecommitdiff
path: root/chromium/headless
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2017-01-04 14:17:57 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2017-01-05 10:05:06 +0000
commit39d357e3248f80abea0159765ff39554affb40db (patch)
treeaba0e6bfb76de0244bba0f5fdbd64b830dd6e621 /chromium/headless
parent87778abf5a1f89266f37d1321b92a21851d8244d (diff)
downloadqtwebengine-chromium-39d357e3248f80abea0159765ff39554affb40db.tar.gz
BASELINE: Update Chromium to 55.0.2883.105
And updates ninja to 1.7.2 Change-Id: I20d43c737f82764d857ada9a55586901b18b9243 Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/headless')
-rw-r--r--chromium/headless/BUILD.gn356
-rw-r--r--chromium/headless/DEPS4
-rw-r--r--chromium/headless/app/headless_shell.cc116
-rw-r--r--chromium/headless/app/headless_shell_switches.cc26
-rw-r--r--chromium/headless/app/headless_shell_switches.h6
-rw-r--r--chromium/headless/lib/OWNERS2
-rw-r--r--chromium/headless/lib/browser/client_api_generator.py45
-rw-r--r--chromium/headless/lib/browser/client_api_generator_unittest.py52
-rw-r--r--chromium/headless/lib/browser/domain_cc.template34
-rw-r--r--chromium/headless/lib/browser/domain_h.template33
-rw-r--r--chromium/headless/lib/browser/headless_browser_context_impl.cc249
-rw-r--r--chromium/headless/lib/browser/headless_browser_context_impl.h45
-rw-r--r--chromium/headless/lib/browser/headless_browser_context_options.cc78
-rw-r--r--chromium/headless/lib/browser/headless_browser_context_options.h78
-rw-r--r--chromium/headless/lib/browser/headless_browser_impl.cc135
-rw-r--r--chromium/headless/lib/browser/headless_browser_impl.h45
-rw-r--r--chromium/headless/lib/browser/headless_browser_main_parts.cc27
-rw-r--r--chromium/headless/lib/browser/headless_browser_main_parts.h11
-rw-r--r--chromium/headless/lib/browser/headless_content_browser_client.cc24
-rw-r--r--chromium/headless/lib/browser/headless_content_browser_client.h4
-rw-r--r--chromium/headless/lib/browser/headless_devtools.cc86
-rw-r--r--chromium/headless/lib/browser/headless_devtools.h11
-rw-r--r--chromium/headless/lib/browser/headless_devtools_client_impl.cc39
-rw-r--r--chromium/headless/lib/browser/headless_devtools_client_impl.h24
-rw-r--r--chromium/headless/lib/browser/headless_devtools_manager_delegate.cc167
-rw-r--r--chromium/headless/lib/browser/headless_devtools_manager_delegate.h56
-rw-r--r--chromium/headless/lib/browser/headless_screen.cc3
-rw-r--r--chromium/headless/lib/browser/headless_screen.h3
-rw-r--r--chromium/headless/lib/browser/headless_url_request_context_getter.cc25
-rw-r--r--chromium/headless/lib/browser/headless_url_request_context_getter.h4
-rw-r--r--chromium/headless/lib/browser/headless_web_contents_impl.cc122
-rw-r--r--chromium/headless/lib/browser/headless_web_contents_impl.h34
-rw-r--r--chromium/headless/lib/browser/type_conversions_h.template6
-rw-r--r--chromium/headless/lib/browser/types_h.template4
-rw-r--r--chromium/headless/lib/embedder_mojo_browsertest.cc247
-rw-r--r--chromium/headless/lib/embedder_test.mojom11
-rw-r--r--chromium/headless/lib/headless_browser_browsertest.cc553
-rw-r--r--chromium/headless/lib/headless_browser_context_browsertest.cc124
-rw-r--r--chromium/headless/lib/headless_browsertest_resource_ids7
-rw-r--r--chromium/headless/lib/headless_browsertest_resources.grd16
-rw-r--r--chromium/headless/lib/headless_content_main_delegate.cc12
-rw-r--r--chromium/headless/lib/headless_devtools_client_browsertest.cc487
-rw-r--r--chromium/headless/lib/headless_web_contents_browsertest.cc28
-rw-r--r--chromium/headless/lib/renderer/headless_content_renderer_client.cc3
-rw-r--r--chromium/headless/lib/resources/devtools_discovery_page.html58
-rw-r--r--chromium/headless/lib/resources/headless_lib_resources.grd3
-rw-r--r--chromium/headless/public/domains/types_unittest.cc14
-rw-r--r--chromium/headless/public/headless_browser.cc38
-rw-r--r--chromium/headless/public/headless_browser.h105
-rw-r--r--chromium/headless/public/headless_browser_context.h101
-rw-r--r--chromium/headless/public/headless_devtools_client.h8
-rw-r--r--chromium/headless/public/headless_web_contents.h61
-rw-r--r--chromium/headless/public/internal/value_conversions.h8
-rw-r--r--chromium/headless/public/util/black_hole_protocol_handler.cc66
-rw-r--r--chromium/headless/public/util/black_hole_protocol_handler.h31
-rw-r--r--chromium/headless/public/util/deterministic_dispatcher.cc95
-rw-r--r--chromium/headless/public/util/deterministic_dispatcher.h62
-rw-r--r--chromium/headless/public/util/deterministic_dispatcher_test.cc113
-rw-r--r--chromium/headless/public/util/deterministic_http_protocol_handler.cc80
-rw-r--r--chromium/headless/public/util/deterministic_http_protocol_handler.h59
-rw-r--r--chromium/headless/public/util/error_reporter.cc2
-rw-r--r--chromium/headless/public/util/expedited_dispatcher.cc39
-rw-r--r--chromium/headless/public/util/expedited_dispatcher.h41
-rw-r--r--chromium/headless/public/util/expedited_dispatcher_test.cc86
-rw-r--r--chromium/headless/public/util/generic_url_request_job.cc189
-rw-r--r--chromium/headless/public/util/generic_url_request_job.h132
-rw-r--r--chromium/headless/public/util/generic_url_request_job_test.cc366
-rw-r--r--chromium/headless/public/util/http_url_fetcher.cc187
-rw-r--r--chromium/headless/public/util/http_url_fetcher.h42
-rw-r--r--chromium/headless/public/util/in_memory_protocol_handler.cc37
-rw-r--r--chromium/headless/public/util/in_memory_protocol_handler.h46
-rw-r--r--chromium/headless/public/util/in_memory_request_job.cc65
-rw-r--r--chromium/headless/public/util/in_memory_request_job.h45
-rw-r--r--chromium/headless/public/util/managed_dispatch_url_request_job.cc47
-rw-r--r--chromium/headless/public/util/managed_dispatch_url_request_job.h61
-rw-r--r--chromium/headless/public/util/testing/fake_managed_dispatch_url_request_job.cc17
-rw-r--r--chromium/headless/public/util/testing/fake_managed_dispatch_url_request_job.h49
-rw-r--r--chromium/headless/public/util/testing/generic_url_request_mocks.cc172
-rw-r--r--chromium/headless/public/util/testing/generic_url_request_mocks.h155
-rw-r--r--chromium/headless/public/util/url_fetcher.cc40
-rw-r--r--chromium/headless/public/util/url_fetcher.h74
-rw-r--r--chromium/headless/public/util/url_request_dispatcher.h45
-rw-r--r--chromium/headless/public/util/user_agent.cc20
-rw-r--r--chromium/headless/public/util/user_agent.h18
84 files changed, 5628 insertions, 691 deletions
diff --git a/chromium/headless/BUILD.gn b/chromium/headless/BUILD.gn
index 27b83f03b5d..8e5970bde4a 100644
--- a/chromium/headless/BUILD.gn
+++ b/chromium/headless/BUILD.gn
@@ -2,6 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import("//mojo/public/tools/bindings/mojom.gni")
import("//testing/test.gni")
import("//tools/grit/grit_rule.gni")
import("//tools/grit/repack.gni")
@@ -58,6 +59,74 @@ grit("headless_lib_resources_grit") {
]
}
+generated_devtools_api = [
+ "$target_gen_dir/public/domains/accessibility.cc",
+ "$target_gen_dir/public/domains/accessibility.h",
+ "$target_gen_dir/public/domains/animation.cc",
+ "$target_gen_dir/public/domains/animation.h",
+ "$target_gen_dir/public/domains/application_cache.cc",
+ "$target_gen_dir/public/domains/application_cache.h",
+ "$target_gen_dir/public/domains/browser.cc",
+ "$target_gen_dir/public/domains/browser.h",
+ "$target_gen_dir/public/domains/cache_storage.cc",
+ "$target_gen_dir/public/domains/cache_storage.h",
+ "$target_gen_dir/public/domains/console.cc",
+ "$target_gen_dir/public/domains/console.h",
+ "$target_gen_dir/public/domains/css.cc",
+ "$target_gen_dir/public/domains/css.h",
+ "$target_gen_dir/public/domains/database.cc",
+ "$target_gen_dir/public/domains/database.h",
+ "$target_gen_dir/public/domains/debugger.cc",
+ "$target_gen_dir/public/domains/debugger.h",
+ "$target_gen_dir/public/domains/device_orientation.cc",
+ "$target_gen_dir/public/domains/device_orientation.h",
+ "$target_gen_dir/public/domains/dom_debugger.cc",
+ "$target_gen_dir/public/domains/dom_debugger.h",
+ "$target_gen_dir/public/domains/dom.cc",
+ "$target_gen_dir/public/domains/dom.h",
+ "$target_gen_dir/public/domains/dom_storage.cc",
+ "$target_gen_dir/public/domains/dom_storage.h",
+ "$target_gen_dir/public/domains/emulation.cc",
+ "$target_gen_dir/public/domains/emulation.h",
+ "$target_gen_dir/public/domains/heap_profiler.cc",
+ "$target_gen_dir/public/domains/heap_profiler.h",
+ "$target_gen_dir/public/domains/indexeddb.cc",
+ "$target_gen_dir/public/domains/indexeddb.h",
+ "$target_gen_dir/public/domains/input.cc",
+ "$target_gen_dir/public/domains/input.h",
+ "$target_gen_dir/public/domains/inspector.cc",
+ "$target_gen_dir/public/domains/inspector.h",
+ "$target_gen_dir/public/domains/io.cc",
+ "$target_gen_dir/public/domains/io.h",
+ "$target_gen_dir/public/domains/layer_tree.cc",
+ "$target_gen_dir/public/domains/layer_tree.h",
+ "$target_gen_dir/public/domains/log.cc",
+ "$target_gen_dir/public/domains/log.h",
+ "$target_gen_dir/public/domains/memory.cc",
+ "$target_gen_dir/public/domains/memory.h",
+ "$target_gen_dir/public/domains/network.cc",
+ "$target_gen_dir/public/domains/network.h",
+ "$target_gen_dir/public/domains/page.cc",
+ "$target_gen_dir/public/domains/page.h",
+ "$target_gen_dir/public/domains/profiler.cc",
+ "$target_gen_dir/public/domains/profiler.h",
+ "$target_gen_dir/public/domains/rendering.cc",
+ "$target_gen_dir/public/domains/rendering.h",
+ "$target_gen_dir/public/domains/runtime.cc",
+ "$target_gen_dir/public/domains/runtime.h",
+ "$target_gen_dir/public/domains/security.cc",
+ "$target_gen_dir/public/domains/security.h",
+ "$target_gen_dir/public/domains/service_worker.cc",
+ "$target_gen_dir/public/domains/service_worker.h",
+ "$target_gen_dir/public/domains/tracing.cc",
+ "$target_gen_dir/public/domains/tracing.h",
+ "$target_gen_dir/public/domains/type_conversions.h",
+ "$target_gen_dir/public/domains/types.cc",
+ "$target_gen_dir/public/domains/types.h",
+ "$target_gen_dir/public/domains/worker.cc",
+ "$target_gen_dir/public/domains/worker.h",
+]
+
action("gen_devtools_client_api") {
script = "//headless/lib/browser/client_api_generator.py"
deps = [
@@ -67,69 +136,7 @@ action("gen_devtools_client_api") {
"$root_gen_dir/blink/core/inspector/protocol.json",
]
- outputs = [
- "$target_gen_dir/public/domains/accessibility.cc",
- "$target_gen_dir/public/domains/accessibility.h",
- "$target_gen_dir/public/domains/animation.cc",
- "$target_gen_dir/public/domains/animation.h",
- "$target_gen_dir/public/domains/application_cache.cc",
- "$target_gen_dir/public/domains/application_cache.h",
- "$target_gen_dir/public/domains/cache_storage.cc",
- "$target_gen_dir/public/domains/cache_storage.h",
- "$target_gen_dir/public/domains/console.cc",
- "$target_gen_dir/public/domains/console.h",
- "$target_gen_dir/public/domains/css.cc",
- "$target_gen_dir/public/domains/css.h",
- "$target_gen_dir/public/domains/database.cc",
- "$target_gen_dir/public/domains/database.h",
- "$target_gen_dir/public/domains/debugger.cc",
- "$target_gen_dir/public/domains/debugger.h",
- "$target_gen_dir/public/domains/device_orientation.cc",
- "$target_gen_dir/public/domains/device_orientation.h",
- "$target_gen_dir/public/domains/dom_debugger.cc",
- "$target_gen_dir/public/domains/dom_debugger.h",
- "$target_gen_dir/public/domains/dom.cc",
- "$target_gen_dir/public/domains/dom.h",
- "$target_gen_dir/public/domains/dom_storage.cc",
- "$target_gen_dir/public/domains/dom_storage.h",
- "$target_gen_dir/public/domains/emulation.cc",
- "$target_gen_dir/public/domains/emulation.h",
- "$target_gen_dir/public/domains/heap_profiler.cc",
- "$target_gen_dir/public/domains/heap_profiler.h",
- "$target_gen_dir/public/domains/indexeddb.cc",
- "$target_gen_dir/public/domains/indexeddb.h",
- "$target_gen_dir/public/domains/input.cc",
- "$target_gen_dir/public/domains/input.h",
- "$target_gen_dir/public/domains/inspector.cc",
- "$target_gen_dir/public/domains/inspector.h",
- "$target_gen_dir/public/domains/io.cc",
- "$target_gen_dir/public/domains/io.h",
- "$target_gen_dir/public/domains/layer_tree.cc",
- "$target_gen_dir/public/domains/layer_tree.h",
- "$target_gen_dir/public/domains/memory.cc",
- "$target_gen_dir/public/domains/memory.h",
- "$target_gen_dir/public/domains/network.cc",
- "$target_gen_dir/public/domains/network.h",
- "$target_gen_dir/public/domains/page.cc",
- "$target_gen_dir/public/domains/page.h",
- "$target_gen_dir/public/domains/profiler.cc",
- "$target_gen_dir/public/domains/profiler.h",
- "$target_gen_dir/public/domains/rendering.cc",
- "$target_gen_dir/public/domains/rendering.h",
- "$target_gen_dir/public/domains/runtime.cc",
- "$target_gen_dir/public/domains/runtime.h",
- "$target_gen_dir/public/domains/security.cc",
- "$target_gen_dir/public/domains/security.h",
- "$target_gen_dir/public/domains/service_worker.cc",
- "$target_gen_dir/public/domains/service_worker.h",
- "$target_gen_dir/public/domains/tracing.cc",
- "$target_gen_dir/public/domains/tracing.h",
- "$target_gen_dir/public/domains/type_conversions.h",
- "$target_gen_dir/public/domains/types.cc",
- "$target_gen_dir/public/domains/types.h",
- "$target_gen_dir/public/domains/worker.cc",
- "$target_gen_dir/public/domains/worker.h",
- ]
+ outputs = generated_devtools_api
sources = [
"lib/browser/domain_cc.template",
@@ -148,115 +155,82 @@ action("gen_devtools_client_api") {
}
static_library("headless_lib") {
- sources = [
- "$target_gen_dir/public/domains/accessibility.cc",
- "$target_gen_dir/public/domains/accessibility.h",
- "$target_gen_dir/public/domains/animation.cc",
- "$target_gen_dir/public/domains/animation.h",
- "$target_gen_dir/public/domains/application_cache.cc",
- "$target_gen_dir/public/domains/application_cache.h",
- "$target_gen_dir/public/domains/cache_storage.cc",
- "$target_gen_dir/public/domains/cache_storage.h",
- "$target_gen_dir/public/domains/console.cc",
- "$target_gen_dir/public/domains/console.h",
- "$target_gen_dir/public/domains/css.cc",
- "$target_gen_dir/public/domains/css.h",
- "$target_gen_dir/public/domains/database.cc",
- "$target_gen_dir/public/domains/database.h",
- "$target_gen_dir/public/domains/debugger.cc",
- "$target_gen_dir/public/domains/debugger.h",
- "$target_gen_dir/public/domains/device_orientation.cc",
- "$target_gen_dir/public/domains/device_orientation.h",
- "$target_gen_dir/public/domains/dom.cc",
- "$target_gen_dir/public/domains/dom.h",
- "$target_gen_dir/public/domains/dom_debugger.cc",
- "$target_gen_dir/public/domains/dom_debugger.h",
- "$target_gen_dir/public/domains/dom_storage.cc",
- "$target_gen_dir/public/domains/dom_storage.h",
- "$target_gen_dir/public/domains/emulation.cc",
- "$target_gen_dir/public/domains/emulation.h",
- "$target_gen_dir/public/domains/heap_profiler.cc",
- "$target_gen_dir/public/domains/heap_profiler.h",
- "$target_gen_dir/public/domains/indexeddb.cc",
- "$target_gen_dir/public/domains/indexeddb.h",
- "$target_gen_dir/public/domains/input.cc",
- "$target_gen_dir/public/domains/input.h",
- "$target_gen_dir/public/domains/inspector.cc",
- "$target_gen_dir/public/domains/inspector.h",
- "$target_gen_dir/public/domains/io.cc",
- "$target_gen_dir/public/domains/io.h",
- "$target_gen_dir/public/domains/layer_tree.cc",
- "$target_gen_dir/public/domains/layer_tree.h",
- "$target_gen_dir/public/domains/memory.cc",
- "$target_gen_dir/public/domains/memory.h",
- "$target_gen_dir/public/domains/network.cc",
- "$target_gen_dir/public/domains/network.h",
- "$target_gen_dir/public/domains/page.cc",
- "$target_gen_dir/public/domains/page.h",
- "$target_gen_dir/public/domains/profiler.cc",
- "$target_gen_dir/public/domains/profiler.h",
- "$target_gen_dir/public/domains/rendering.cc",
- "$target_gen_dir/public/domains/rendering.h",
- "$target_gen_dir/public/domains/runtime.cc",
- "$target_gen_dir/public/domains/runtime.h",
- "$target_gen_dir/public/domains/security.cc",
- "$target_gen_dir/public/domains/security.h",
- "$target_gen_dir/public/domains/service_worker.cc",
- "$target_gen_dir/public/domains/service_worker.h",
- "$target_gen_dir/public/domains/tracing.cc",
- "$target_gen_dir/public/domains/tracing.h",
- "$target_gen_dir/public/domains/type_conversions.h",
- "$target_gen_dir/public/domains/types.cc",
- "$target_gen_dir/public/domains/types.h",
- "$target_gen_dir/public/domains/worker.cc",
- "$target_gen_dir/public/domains/worker.h",
- "lib/browser/headless_browser_context_impl.cc",
- "lib/browser/headless_browser_context_impl.h",
- "lib/browser/headless_browser_impl.cc",
- "lib/browser/headless_browser_impl.h",
- "lib/browser/headless_browser_main_parts.cc",
- "lib/browser/headless_browser_main_parts.h",
- "lib/browser/headless_content_browser_client.cc",
- "lib/browser/headless_content_browser_client.h",
- "lib/browser/headless_devtools.cc",
- "lib/browser/headless_devtools.h",
- "lib/browser/headless_devtools_client_impl.cc",
- "lib/browser/headless_devtools_client_impl.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_tree_client.cc",
- "lib/browser/headless_window_tree_client.h",
- "lib/headless_content_client.cc",
- "lib/headless_content_client.h",
- "lib/headless_content_main_delegate.cc",
- "lib/headless_content_main_delegate.h",
- "lib/renderer/headless_content_renderer_client.cc",
- "lib/renderer/headless_content_renderer_client.h",
- "lib/utility/headless_content_utility_client.cc",
- "lib/utility/headless_content_utility_client.h",
- "public/headless_browser.cc",
- "public/headless_browser.h",
- "public/headless_browser_context.h",
- "public/headless_devtools_client.h",
- "public/headless_devtools_host.h",
- "public/headless_devtools_target.h",
- "public/headless_export.h",
- "public/headless_web_contents.h",
- "public/internal/message_dispatcher.h",
- "public/internal/value_conversions.h",
- "public/util/error_reporter.cc",
- "public/util/error_reporter.h",
- ]
+ sources = generated_devtools_api + [
+ "lib/browser/headless_browser_context_impl.cc",
+ "lib/browser/headless_browser_context_impl.h",
+ "lib/browser/headless_browser_context_options.cc",
+ "lib/browser/headless_browser_context_options.h",
+ "lib/browser/headless_browser_impl.cc",
+ "lib/browser/headless_browser_impl.h",
+ "lib/browser/headless_browser_main_parts.cc",
+ "lib/browser/headless_browser_main_parts.h",
+ "lib/browser/headless_content_browser_client.cc",
+ "lib/browser/headless_content_browser_client.h",
+ "lib/browser/headless_devtools.cc",
+ "lib/browser/headless_devtools.h",
+ "lib/browser/headless_devtools_client_impl.cc",
+ "lib/browser/headless_devtools_client_impl.h",
+ "lib/browser/headless_devtools_manager_delegate.cc",
+ "lib/browser/headless_devtools_manager_delegate.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_tree_client.cc",
+ "lib/browser/headless_window_tree_client.h",
+ "lib/headless_content_client.cc",
+ "lib/headless_content_client.h",
+ "lib/headless_content_main_delegate.cc",
+ "lib/headless_content_main_delegate.h",
+ "lib/renderer/headless_content_renderer_client.cc",
+ "lib/renderer/headless_content_renderer_client.h",
+ "lib/utility/headless_content_utility_client.cc",
+ "lib/utility/headless_content_utility_client.h",
+ "public/headless_browser.cc",
+ "public/headless_browser.h",
+ "public/headless_browser_context.h",
+ "public/headless_devtools_client.h",
+ "public/headless_devtools_host.h",
+ "public/headless_devtools_target.h",
+ "public/headless_export.h",
+ "public/headless_web_contents.h",
+ "public/internal/message_dispatcher.h",
+ "public/internal/value_conversions.h",
+ "public/util/black_hole_protocol_handler.cc",
+ "public/util/black_hole_protocol_handler.h",
+ "public/util/deterministic_dispatcher.cc",
+ "public/util/deterministic_dispatcher.h",
+ "public/util/deterministic_http_protocol_handler.cc",
+ "public/util/deterministic_http_protocol_handler.h",
+ "public/util/error_reporter.cc",
+ "public/util/error_reporter.h",
+ "public/util/expedited_dispatcher.cc",
+ "public/util/expedited_dispatcher.h",
+ "public/util/generic_url_request_job.cc",
+ "public/util/generic_url_request_job.h",
+ "public/util/http_url_fetcher.cc",
+ "public/util/http_url_fetcher.h",
+ "public/util/in_memory_protocol_handler.cc",
+ "public/util/in_memory_protocol_handler.h",
+ "public/util/in_memory_request_job.cc",
+ "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/testing/generic_url_request_mocks.cc",
+ "public/util/testing/generic_url_request_mocks.h",
+ "public/util/url_fetcher.cc",
+ "public/util/url_fetcher.h",
+ "public/util/url_request_dispatcher.h",
+ "public/util/user_agent.cc",
+ "public/util/user_agent.h",
+ ]
deps = [
":gen_devtools_client_api",
":pak",
"//base",
- "//components/devtools_http_handler",
"//content/public/app:both",
"//content/public/browser",
"//content/public/child",
@@ -264,6 +238,8 @@ static_library("headless_lib") {
"//content/public/renderer",
"//content/public/utility",
"//net",
+ "//services/shell/public/cpp",
+ "//third_party/mesa:osmesa",
"//ui/aura",
"//ui/base",
"//ui/compositor",
@@ -288,13 +264,19 @@ group("headless_tests") {
test("headless_unittests") {
sources = [
"public/domains/types_unittest.cc",
+ "public/util/deterministic_dispatcher_test.cc",
"public/util/error_reporter_unittest.cc",
+ "public/util/expedited_dispatcher_test.cc",
+ "public/util/generic_url_request_job_test.cc",
+ "public/util/testing/fake_managed_dispatch_url_request_job.cc",
+ "public/util/testing/fake_managed_dispatch_url_request_job.h",
]
deps = [
":headless_lib",
"//base/test:run_all_unittests",
"//base/test:test_support",
+ "//testing/gmock",
"//testing/gtest",
]
}
@@ -316,8 +298,41 @@ action("client_api_generator_tests") {
]
}
+mojom("embedder_mojo_for_testing") {
+ sources = [
+ "lib/embedder_test.mojom",
+ ]
+}
+
+grit("headless_browsertest_resources_grit") {
+ source = "lib/headless_browsertest_resources.grd"
+ outputs = [
+ "grit/headless_browsertest_resources.h",
+ "$root_gen_dir/headless/headless_browsertest_resources.pak",
+ ]
+ grit_flags = [
+ "-E",
+ "mojom_root=" + rebase_path(root_gen_dir),
+ ]
+ deps = [
+ ":embedder_mojo_for_testing__generator",
+ ]
+ resource_ids = "lib/headless_browsertest_resource_ids"
+}
+
+repack("headless_browser_tests_pak") {
+ sources = [
+ "$root_gen_dir/headless/headless_browsertest_resources.pak",
+ ]
+ output = "$root_out_dir/headless_browser_tests.pak"
+ deps = [
+ ":headless_browsertest_resources_grit",
+ ]
+}
+
test("headless_browsertests") {
sources = [
+ "lib/embedder_mojo_browsertest.cc",
"lib/headless_browser_browsertest.cc",
"lib/headless_browser_context_browsertest.cc",
"lib/headless_devtools_client_browsertest.cc",
@@ -331,11 +346,16 @@ test("headless_browsertests") {
"test/test_url_request_job.h",
]
+ data = [
+ "$root_out_dir/headless_browser_tests.pak",
+ ]
+
defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
deps = [
+ ":embedder_mojo_for_testing",
+ ":headless_browser_tests_pak",
"//base",
- "//content/test:browsertest_base",
"//content/test:test_support",
"//headless:headless_lib",
"//testing/gmock",
diff --git a/chromium/headless/DEPS b/chromium/headless/DEPS
index 2e747057cbf..cc93e6b7e6c 100644
--- a/chromium/headless/DEPS
+++ b/chromium/headless/DEPS
@@ -1,11 +1,13 @@
include_rules = [
- "+components/devtools_http_handler",
"+content/public",
+ "+mojo/public",
"+net",
"+ui/base",
"+ui/base/resource",
"+ui/display",
"+ui/gfx",
"+ui/gfx/geometry",
+ "+ui/gl",
"+ui/ozone/public",
+ "+services/shell/public",
]
diff --git a/chromium/headless/app/headless_shell.cc b/chromium/headless/app/headless_shell.cc
index f9e8e3f45d4..c2e3690f53d 100644
--- a/chromium/headless/app/headless_shell.cc
+++ b/chromium/headless/app/headless_shell.cc
@@ -18,12 +18,15 @@
#include "base/strings/string_number_conversions.h"
#include "content/public/common/content_switches.h"
#include "headless/app/headless_shell_switches.h"
+#include "headless/public/domains/emulation.h"
#include "headless/public/domains/page.h"
#include "headless/public/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"
@@ -31,8 +34,10 @@
#include "ui/gfx/geometry/size.h"
using headless::HeadlessBrowser;
+using headless::HeadlessBrowserContext;
using headless::HeadlessDevToolsClient;
using headless::HeadlessWebContents;
+namespace emulation = headless::emulation;
namespace page = headless::page;
namespace runtime = headless::runtime;
@@ -41,25 +46,68 @@ namespace {
const char kDevToolsHttpServerAddress[] = "127.0.0.1";
// Default file name for screenshot. Can be overriden by "--screenshot" switch.
const char kDefaultScreenshotFileName[] = "screenshot.png";
+
+bool ParseWindowSize(std::string window_size, gfx::Size* parsed_window_size) {
+ int width, height = 0;
+ if (sscanf(window_size.c_str(), "%dx%d", &width, &height) >= 2 &&
+ width >= 0 && height >= 0) {
+ parsed_window_size->set_width(width);
+ parsed_window_size->set_height(height);
+ return true;
+ }
+ return false;
}
+} // namespace
// A sample application which demonstrates the use of the headless API.
-class HeadlessShell : public HeadlessWebContents::Observer, page::Observer {
+class HeadlessShell : public HeadlessWebContents::Observer,
+ emulation::ExperimentalObserver,
+ page::Observer {
public:
HeadlessShell()
: browser_(nullptr),
devtools_client_(HeadlessDevToolsClient::Create()),
web_contents_(nullptr),
- processed_page_ready_(false) {}
+ processed_page_ready_(false),
+ browser_context_(nullptr) {}
~HeadlessShell() override {}
void OnStart(HeadlessBrowser* browser) {
browser_ = browser;
- HeadlessWebContents::Builder builder(browser_->CreateWebContentsBuilder());
+ HeadlessBrowserContext::Builder context_builder =
+ browser_->CreateBrowserContextBuilder();
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ headless::switches::kDeterministicFetch)) {
+ deterministic_dispatcher_.reset(
+ new headless::DeterministicDispatcher(browser_->BrowserIOThread()));
+
+ headless::ProtocolHandlerMap protocol_handlers;
+ protocol_handlers[url::kHttpScheme] =
+ base::MakeUnique<headless::DeterministicHttpProtocolHandler>(
+ deterministic_dispatcher_.get(), browser->BrowserIOThread());
+ protocol_handlers[url::kHttpsScheme] =
+ base::MakeUnique<headless::DeterministicHttpProtocolHandler>(
+ deterministic_dispatcher_.get(), browser->BrowserIOThread());
+
+ context_builder.SetProtocolHandlers(std::move(protocol_handlers));
+ }
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ headless::switches::kHideScrollbars)) {
+ context_builder.SetOverrideWebPreferencesCallback(
+ base::Bind([](headless::WebPreferences* preferences) {
+ preferences->hide_scrollbars = true;
+ }));
+ }
+ browser_context_ = context_builder.Build();
+
+ 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[0].empty())
builder.SetInitialURL(GURL(args[0]));
@@ -76,11 +124,13 @@ class HeadlessShell : public HeadlessWebContents::Observer, page::Observer {
if (!web_contents_)
return;
if (!RemoteDebuggingEnabled()) {
+ devtools_client_->GetEmulation()->GetExperimental()->RemoveObserver(this);
devtools_client_->GetPage()->RemoveObserver(this);
web_contents_->GetDevToolsTarget()->DetachClient(devtools_client_.get());
}
web_contents_->RemoveObserver(this);
web_contents_ = nullptr;
+ browser_context_->Close();
browser_->Shutdown();
}
@@ -93,7 +143,26 @@ class HeadlessShell : public HeadlessWebContents::Observer, page::Observer {
devtools_client_->GetPage()->Enable();
// Check if the document had already finished loading by the time we
// attached.
- PollReadyState();
+
+ devtools_client_->GetEmulation()->GetExperimental()->AddObserver(this);
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ headless::switches::kVirtualTimeBudget)) {
+ std::string budget_ms_ascii =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ headless::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();
+ }
// TODO(skyostil): Implement more features to demonstrate the devtools API.
}
@@ -122,8 +191,18 @@ class HeadlessShell : public HeadlessWebContents::Observer, page::Observer {
}
}
+ // emulation::Observer implementation:
+ void OnVirtualTimeBudgetExpired(
+ const emulation::VirtualTimeBudgetExpiredParams& params) override {
+ OnPageReady();
+ }
+
// page::Observer implementation:
void OnLoadEventFired(const page::LoadEventFiredParams& params) override {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ headless::switches::kVirtualTimeBudget)) {
+ return;
+ }
OnPageReady();
}
@@ -156,8 +235,9 @@ class HeadlessShell : public HeadlessWebContents::Observer, page::Observer {
}
void OnDomFetched(std::unique_ptr<runtime::EvaluateResult> result) {
- if (result->GetWasThrown()) {
- LOG(ERROR) << "Failed to evaluate document.body.innerHTML";
+ if (result->HasExceptionDetails()) {
+ LOG(ERROR) << "Failed to evaluate document.body.innerHTML: "
+ << result->GetExceptionDetails()->GetText();
} else {
std::string dom;
if (result->GetResult()->GetValue()->GetAsString(&dom)) {
@@ -280,6 +360,8 @@ class HeadlessShell : public HeadlessWebContents::Observer, page::Observer {
HeadlessWebContents* web_contents_;
bool processed_page_ready_;
std::unique_ptr<net::FileStream> screenshot_file_stream_;
+ HeadlessBrowserContext* browser_context_;
+ std::unique_ptr<headless::DeterministicDispatcher> deterministic_dispatcher_;
DISALLOW_COPY_AND_ASSIGN(HeadlessShell);
};
@@ -334,6 +416,28 @@ int main(int argc, const char** argv) {
command_line.GetSwitchValueASCII(switches::kHostResolverRules));
}
+ if (command_line.HasSwitch(headless::switches::kUseGL)) {
+ builder.SetGLImplementation(
+ command_line.GetSwitchValueASCII(headless::switches::kUseGL));
+ }
+
+ if (command_line.HasSwitch(headless::switches::kUserDataDir)) {
+ builder.SetUserDataDir(
+ command_line.GetSwitchValuePath(headless::switches::kUserDataDir));
+ builder.SetIncognitoMode(false);
+ }
+
+ if (command_line.HasSwitch(headless::switches::kWindowSize)) {
+ std::string window_size =
+ command_line.GetSwitchValueASCII(headless::switches::kWindowSize);
+ gfx::Size parsed_window_size;
+ if (!ParseWindowSize(window_size, &parsed_window_size)) {
+ LOG(ERROR) << "Malformed window size";
+ return EXIT_FAILURE;
+ }
+ builder.SetWindowSize(parsed_window_size);
+ }
+
return HeadlessBrowserMain(
builder.Build(),
base::Bind(&HeadlessShell::OnStart, base::Unretained(&shell)));
diff --git a/chromium/headless/app/headless_shell_switches.cc b/chromium/headless/app/headless_shell_switches.cc
index 4a62454cc46..18cf95d3e1b 100644
--- a/chromium/headless/app/headless_shell_switches.cc
+++ b/chromium/headless/app/headless_shell_switches.cc
@@ -7,9 +7,17 @@
namespace headless {
namespace switches {
+// 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.
+const char kDeterministicFetch[] = "deterministic-fetch";
+
// Instructs headless_shell to print document.body.innerHTML to stdout.
const char kDumpDom[] = "dump-dom";
+// Hide scrollbars from screenshots.
+const char kHideScrollbars[] = "hide-scrollbars";
+
// Uses a specified proxy server, overrides system settings. This switch only
// affects HTTP and HTTPS requests.
const char kProxyServer[] = "proxy-server";
@@ -27,5 +35,23 @@ const char kRepl[] = "repl";
// Save a screenshot of the loaded page.
const char kScreenshot[] = "screenshot";
+// Sets the GL implementation to use. Use a blank string to disable GL
+// rendering.
+const char kUseGL[] = "use-gl";
+
+// Directory where the browser stores the user profile.
+const char kUserDataDir[] = "user-data-dir";
+
+// If set the system waits the specified number of virtual milliseconds before
+// deeming the page to be ready. For determinism virtual time does not advance
+// while there are pending network fetches (i.e no timers will fire). Once all
+// network fetches have completed, timers fire and if the system runs out of
+// virtual time is fastforwarded so the next timer fires immediatley, until the
+// specified virtual time budget is exhausted.
+const char kVirtualTimeBudget[] = "virtual-time-budget";
+
+// Sets the initial window size. Provided as string in the format "800x600".
+const char kWindowSize[] = "window-size";
+
} // namespace switches
} // namespace headless
diff --git a/chromium/headless/app/headless_shell_switches.h b/chromium/headless/app/headless_shell_switches.h
index 7f5208b473e..3806b43232d 100644
--- a/chromium/headless/app/headless_shell_switches.h
+++ b/chromium/headless/app/headless_shell_switches.h
@@ -7,11 +7,17 @@
namespace headless {
namespace switches {
+extern const char kDeterministicFetch[];
extern const char kDumpDom[];
+extern const char kHideScrollbars[];
extern const char kProxyServer[];
extern const char kRemoteDebuggingAddress[];
extern const char kRepl[];
extern const char kScreenshot[];
+extern const char kUseGL[];
+extern const char kUserDataDir[];
+extern const char kVirtualTimeBudget[];
+extern const char kWindowSize[];
} // namespace switches
} // namespace headless
diff --git a/chromium/headless/lib/OWNERS b/chromium/headless/lib/OWNERS
new file mode 100644
index 00000000000..08850f42120
--- /dev/null
+++ b/chromium/headless/lib/OWNERS
@@ -0,0 +1,2 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/chromium/headless/lib/browser/client_api_generator.py b/chromium/headless/lib/browser/client_api_generator.py
index 40f1206d17b..bb93ede97aa 100644
--- a/chromium/headless/lib/browser/client_api_generator.py
+++ b/chromium/headless/lib/browser/client_api_generator.py
@@ -59,9 +59,17 @@ def CamelCaseToHackerStyle(name):
return name.lower()
-def MangleEnum(value):
- # Rename NULL enumeration values to avoid a clash with the actual NULL.
- return 'NONE' if value == 'NULL' else value
+def SanitizeLiteral(literal):
+ return {
+ # Rename null enumeration values to avoid a clash with the NULL macro.
+ 'null': 'none',
+ # Rename mathematical constants to avoid colliding with C macros.
+ 'Infinity': 'InfinityValue',
+ '-Infinity': 'NegativeInfinityValue',
+ 'NaN': 'NaNValue',
+ # Turn negative zero into a safe identifier.
+ '-0': 'NegativeZeroValue',
+ }.get(literal, literal)
def InitializeJinjaEnv(cache_dir):
@@ -77,7 +85,7 @@ def InitializeJinjaEnv(cache_dir):
'to_title_case': ToTitleCase,
'dash_to_camelcase': DashToCamelCase,
'camelcase_to_hacker_style': CamelCaseToHackerStyle,
- 'mangle_enum': MangleEnum,
+ 'sanitize_literal': SanitizeLiteral,
})
jinja_env.add_extension('jinja2.ext.loopcontrols')
return jinja_env
@@ -353,27 +361,33 @@ def SynthesizeEventTypes(json_api):
domain['types'].append(event_type)
-def PatchHiddenCommandsAndEvents(json_api):
+def PatchExperimentalCommandsAndEvents(json_api):
"""
- Mark all commands and events in hidden domains as hidden and make sure hidden
- commands have at least empty parameters and return values.
+ Mark all commands and events in experimental domains as experimental
+ and make sure experimental commands have at least empty parameters
+ and return values.
"""
for domain in json_api['domains']:
- if domain.get('hidden', False):
+ if domain.get('experimental', False):
for command in domain.get('commands', []):
- command['hidden'] = True
+ command['experimental'] = True
for event in domain.get('events', []):
- event['hidden'] = True
+ event['experimental'] = True
+
+
+def EnsureCommandsHaveParametersAndReturnTypes(json_api):
+ """
+ Make sure all commands have at least empty parameters and return values. This
+ guarantees API compatibility if a previously experimental command is made
+ stable.
+ """
+ for domain in json_api['domains']:
for command in domain.get('commands', []):
- if not command.get('hidden', False):
- continue
if not 'parameters' in command:
command['parameters'] = []
if not 'returns' in command:
command['returns'] = []
for event in domain.get('events', []):
- if not event.get('hidden', False):
- continue
if not 'parameters' in event:
event['parameters'] = []
@@ -412,7 +426,8 @@ def GenerateDomains(jinja_env, output_dirname, json_api, class_name,
if __name__ == '__main__':
json_api, output_dirname = ParseArguments(sys.argv[1:])
jinja_env = InitializeJinjaEnv(output_dirname)
- PatchHiddenCommandsAndEvents(json_api)
+ PatchExperimentalCommandsAndEvents(json_api)
+ EnsureCommandsHaveParametersAndReturnTypes(json_api)
SynthesizeCommandTypes(json_api)
SynthesizeEventTypes(json_api)
PatchFullQualifiedRefs(json_api)
diff --git a/chromium/headless/lib/browser/client_api_generator_unittest.py b/chromium/headless/lib/browser/client_api_generator_unittest.py
index 0cd7df3fd37..d9d3f81dea7 100644
--- a/chromium/headless/lib/browser/client_api_generator_unittest.py
+++ b/chromium/headless/lib/browser/client_api_generator_unittest.py
@@ -35,9 +35,11 @@ class ClientApiGeneratorTest(unittest.TestCase):
self.assertEqual(client_api_generator.CamelCaseToHackerStyle('LoLoLoL'),
'lo_lo_lol')
- def test_MangleEnum(self):
- self.assertEqual(client_api_generator.MangleEnum('FOO'), 'FOO')
- self.assertEqual(client_api_generator.MangleEnum('NULL'), 'NONE')
+ def test_SanitizeLiteralEnum(self):
+ self.assertEqual(client_api_generator.SanitizeLiteral('foo'), 'foo')
+ self.assertEqual(client_api_generator.SanitizeLiteral('null'), 'none')
+ self.assertEqual(client_api_generator.SanitizeLiteral('Infinity'),
+ 'InfinityValue')
def test_PatchFullQualifiedRefs(self):
json_api = {
@@ -349,12 +351,12 @@ class ClientApiGeneratorTest(unittest.TestCase):
types = json_api['domains'][0]['types']
self.assertListEqual(types, expected_types)
- def test_PatchHiddenDomains(self):
+ def test_PatchExperimentalDomains(self):
json_api = {
'domains': [
{
'domain': 'domain',
- 'hidden': True,
+ 'experimental': True,
'commands': [
{
'name': 'FooCommand',
@@ -368,37 +370,13 @@ class ClientApiGeneratorTest(unittest.TestCase):
}
]
}
- expected_types = [
- {
- 'type': 'object',
- 'id': 'FooCommandParams',
- 'description': 'Parameters for the FooCommand command.',
- 'properties': [],
- },
- {
- 'type': 'object',
- 'id': 'FooCommandResult',
- 'description': 'Result for the FooCommand command.',
- 'properties': [],
- },
- {
- 'type': 'object',
- 'id': 'BarEventParams',
- 'description': 'Parameters for the BarEvent event.',
- 'properties': [],
- }
- ]
- client_api_generator.PatchHiddenCommandsAndEvents(json_api)
- client_api_generator.SynthesizeCommandTypes(json_api)
- client_api_generator.SynthesizeEventTypes(json_api)
+ client_api_generator.PatchExperimentalCommandsAndEvents(json_api)
for command in json_api['domains'][0]['commands']:
- self.assertTrue(command['hidden'])
+ self.assertTrue(command['experimental'])
for event in json_api['domains'][0]['events']:
- self.assertTrue(command['hidden'])
- types = json_api['domains'][0]['types']
- self.assertListEqual(types, expected_types)
+ self.assertTrue(command['experimental'])
- def test_PatchHiddenCommandsAndEvents(self):
+ def test_EnsureCommandsHaveParametersAndReturnTypes(self):
json_api = {
'domains': [
{
@@ -406,13 +384,11 @@ class ClientApiGeneratorTest(unittest.TestCase):
'commands': [
{
'name': 'FooCommand',
- 'hidden': True,
}
],
'events': [
{
'name': 'BarEvent',
- 'hidden': True,
}
]
}
@@ -438,13 +414,9 @@ class ClientApiGeneratorTest(unittest.TestCase):
'properties': [],
}
]
- client_api_generator.PatchHiddenCommandsAndEvents(json_api)
+ client_api_generator.EnsureCommandsHaveParametersAndReturnTypes(json_api)
client_api_generator.SynthesizeCommandTypes(json_api)
client_api_generator.SynthesizeEventTypes(json_api)
- for command in json_api['domains'][0]['commands']:
- self.assertTrue(command['hidden'])
- for event in json_api['domains'][0]['events']:
- self.assertTrue(command['hidden'])
types = json_api['domains'][0]['types']
self.assertListEqual(types, expected_types)
diff --git a/chromium/headless/lib/browser/domain_cc.template b/chromium/headless/lib/browser/domain_cc.template
index 118d996627e..643afe749a8 100644
--- a/chromium/headless/lib/browser/domain_cc.template
+++ b/chromium/headless/lib/browser/domain_cc.template
@@ -30,26 +30,15 @@ void Domain::RemoveObserver(Observer* observer) {
{# Skip redirected commands. #}
{% if "redirect" in command %}{% continue %}{% endif %}
- {% set class_name = 'ExperimentalDomain' if command.hidden else 'Domain' %}
+ {% set class_name = 'ExperimentalDomain' if command.experimental else 'Domain' %}
{% set method_name = command.name | to_title_case %}
- {% if "parameters" in command and "returns" in command %}
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));
- {% elif "parameters" in command %}
-void {{class_name}}::{{method_name}}(std::unique_ptr<{{method_name}}Params> params, base::Callback<void()> callback) {
- dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", params->Serialize(), std::move(callback));
- {% elif "returns" in command %}
-void {{class_name}}::{{method_name}}(base::Callback<void(std::unique_ptr<{{method_name}}Result>)> callback) {
- dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", base::Bind(&Domain::Handle{{method_name}}Response, std::move(callback)));
- {% else %}
-void {{class_name}}::{{method_name}}(base::Callback<void()> callback) {
- dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", std::move(callback));
- {% endif %}
}
{# Generate convenience methods that take the required parameters directly. #}
{% if not "parameters" in command %}{% continue %}{% endif %}
- {# Don't generate these for hidden commands. #}
- {% if command.hidden %}{% continue %}{% endif %}
+ {# Don't generate these for experimental commands. #}
+ {% if command.experimental %}{% continue %}{% endif %}
void {{class_name}}::{{method_name}}({##}
{% for parameter in command.parameters -%}
@@ -59,8 +48,8 @@ void {{class_name}}::{{method_name}}({##}
{% if not loop.first %}, {% endif %}
{{resolve_type(parameter).pass_type}} {{parameter.name | camelcase_to_hacker_style -}}
{% endfor %}
- {% if "parameters" in command and not command.parameters[0].get("optional", False) %}, {% endif %}{# -#}
- {% if "returns" in command -%}
+ {% if command.get("parameters", []) and not command.parameters[0].get("optional", False) %}, {% endif %}{# -#}
+ {% if command.get("returns", []) -%}
base::Callback<void(std::unique_ptr<{{method_name}}Result>)> callback{##}
{% else -%}
base::Callback<void()> callback{##}
@@ -75,8 +64,19 @@ void {{class_name}}::{{method_name}}({##}
{% endfor %}
.Build();
{# Send the message. #}
- {{method_name}}(std::move(params), std::move(callback));
+ {% if command.get("returns", []) -%}
+ dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", params->Serialize(), base::Bind(&Domain::Handle{{method_name}}Response, callback));
+ {% else %}
+ dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", params->Serialize(), std::move(callback));
+ {% endif %}
+}
+ {# If the command has no return value, generate a convenience method that #}
+ {# accepts a base::Callback<void()> together with the parameters object. #}
+ {% if not command.get("returns", []) %}
+void {{class_name}}::{{method_name}}(std::unique_ptr<{{method_name}}Params> params, base::Callback<void()> callback) {
+ dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", params->Serialize(), std::move(callback));
}
+ {% endif %}
{% endfor %}
{# Generate response handlers for commands that need them. #}
diff --git a/chromium/headless/lib/browser/domain_h.template b/chromium/headless/lib/browser/domain_h.template
index 607b5be16f2..2d146a70b52 100644
--- a/chromium/headless/lib/browser/domain_h.template
+++ b/chromium/headless/lib/browser/domain_h.template
@@ -20,18 +20,10 @@
{% if command.description %}
// {{ command.description }}
{% endif %}
- {% if "parameters" in command and "returns" in command %}
void {{method_name}}(std::unique_ptr<{{method_name}}Params> params, base::Callback<void(std::unique_ptr<{{method_name}}Result>)> callback = base::Callback<void(std::unique_ptr<{{method_name}}Result>)>());
- {% elif "parameters" in command %}
- void {{method_name}}(std::unique_ptr<{{method_name}}Params> params, base::Callback<void()> callback = base::Callback<void()>());
- {% elif "returns" in command %}
- void {{method_name}}(base::Callback<void(std::unique_ptr<{{method_name}}Result>)> callback = base::Callback<void(std::unique_ptr<{{method_name}}Result>)>());
- {% else %}
- void {{method_name}}(base::Callback<void()> callback = base::Callback<void()>());
- {% endif %}
{# Generate convenience methods that take the required parameters directly. #}
- {# Don't generate these for hidden commands. #}
- {% if "parameters" in command and not command.hidden %}
+ {# Don't generate these for experimental commands. #}
+ {% if "parameters" in command and not command.experimental %}
void {{method_name}}({##}
{% for parameter in command.parameters -%}
{% if parameter.get("optional", False) -%}
@@ -40,12 +32,17 @@
{% if not loop.first %}, {% endif %}
{{resolve_type(parameter).pass_type}} {{parameter.name | camelcase_to_hacker_style -}}
{% endfor %}
- {% if "parameters" in command and not command.parameters[0].get("optional", False) %}, {% endif %}{# -#}
- {% if "returns" in command -%}
+ {% if command.get("parameters", []) and not command.parameters[0].get("optional", False) %}, {% endif %}{# -#}
+ {% if command.get("returns", []) -%}
base::Callback<void(std::unique_ptr<{{method_name}}Result>)> callback = base::Callback<void(std::unique_ptr<{{method_name}}Result>)>(){##}
{% else -%}
base::Callback<void()> callback = base::Callback<void()>(){##}
{% endif %});
+ {# If the command has no return value, generate a convenience method that #}
+ {# accepts a base::Callback<void()> together with the parameters object. #}
+ {% if not command.get("returns", []) %}
+ void {{method_name}}(std::unique_ptr<{{method_name}}Params> params, base::Callback<void()> callback);
+ {% endif %}
{% endif %}
{% endmacro %}
@@ -71,9 +68,9 @@ class HEADLESS_EXPORT Observer : public ExperimentalObserver {
virtual ~Observer() {}
{% for event in domain.events %}
{% if event.description %}
- // {% if event.hidden %}Experimental: {% endif %}{{event.description}}
+ // {% if event.experimental %}Experimental: {% endif %}{{event.description}}
{% endif %}
- virtual void On{{event.name | to_title_case}}(const {{event.name | to_title_case}}Params& params) {% if event.hidden %}final {% endif %}{}
+ virtual void On{{event.name | to_title_case}}(const {{event.name | to_title_case}}Params& params) {% if event.experimental %}final {% endif %}{}
{% endfor %}
};
{% endif %}
@@ -98,8 +95,8 @@ class HEADLESS_EXPORT Domain {
{% for command in domain.commands %}
{# Skip redirected commands. #}
{% if "redirect" in command %}{% continue %}{% endif %}
- {# Skip hidden commands. #}
- {% if command.hidden %}{% continue %}{% endif %}
+ {# Skip experimental commands. #}
+ {% if command.experimental %}{% continue %}{% endif %}
{{ command_decl(command) }}
{% endfor %}
protected:
@@ -143,8 +140,8 @@ class ExperimentalDomain : public Domain {
{% for command in domain.commands %}
{# Skip redirected commands. #}
{% if "redirect" in command %}{% continue %}{% endif %}
- {# Skip non-hidden commands. #}
- {% if not command.hidden %}{% continue %}{% endif %}
+ {# Skip non-experimental commands. #}
+ {% if not command.experimental %}{% continue %}{% endif %}
{{ command_decl(command) }}
{% endfor %}
diff --git a/chromium/headless/lib/browser/headless_browser_context_impl.cc b/chromium/headless/lib/browser/headless_browser_context_impl.cc
index ed0188050ec..df65523f34b 100644
--- a/chromium/headless/lib/browser/headless_browser_context_impl.cc
+++ b/chromium/headless/lib/browser/headless_browser_context_impl.cc
@@ -5,18 +5,30 @@
#include "headless/lib/browser/headless_browser_context_impl.h"
#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+#include "base/guid.h"
#include "base/memory/ptr_util.h"
#include "base/path_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/storage_partition.h"
+#include "headless/lib/browser/headless_browser_context_options.h"
#include "headless/lib/browser/headless_browser_impl.h"
#include "headless/lib/browser/headless_url_request_context_getter.h"
+#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 {
+namespace {
+const char kHeadlessMojomProtocol[] = "headless-mojom";
+}
+
// Contains net::URLRequestContextGetter required for resource loading.
// Must be destructed on the IO thread as per content::ResourceContext
// requirements.
@@ -67,15 +79,23 @@ net::URLRequestContext* HeadlessResourceContext::GetRequestContext() {
}
HeadlessBrowserContextImpl::HeadlessBrowserContextImpl(
- ProtocolHandlerMap protocol_handlers,
- HeadlessBrowser::Options* options)
- : protocol_handlers_(std::move(protocol_handlers)),
- options_(options),
- resource_context_(new HeadlessResourceContext) {
+ HeadlessBrowserImpl* browser,
+ std::unique_ptr<HeadlessBrowserContextOptions> context_options)
+ : browser_(browser),
+ context_options_(std::move(context_options)),
+ resource_context_(new HeadlessResourceContext),
+ id_(base::GenerateGUID()) {
InitWhileIOAllowed();
}
HeadlessBrowserContextImpl::~HeadlessBrowserContextImpl() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ // Destroy all web contents before shutting down storage partitions.
+ web_contents_map_.clear();
+
+ ShutdownStoragePartitions();
+
if (resource_context_) {
content::BrowserThread::DeleteSoon(content::BrowserThread::IO, FROM_HERE,
resource_context_.release());
@@ -85,12 +105,53 @@ HeadlessBrowserContextImpl::~HeadlessBrowserContextImpl() {
// static
HeadlessBrowserContextImpl* HeadlessBrowserContextImpl::From(
HeadlessBrowserContext* browser_context) {
- return reinterpret_cast<HeadlessBrowserContextImpl*>(browser_context);
+ return static_cast<HeadlessBrowserContextImpl*>(browser_context);
+}
+
+// static
+HeadlessBrowserContextImpl* HeadlessBrowserContextImpl::From(
+ content::BrowserContext* browser_context) {
+ return static_cast<HeadlessBrowserContextImpl*>(browser_context);
+}
+
+// static
+std::unique_ptr<HeadlessBrowserContextImpl> HeadlessBrowserContextImpl::Create(
+ HeadlessBrowserContext::Builder* builder) {
+ return base::WrapUnique(new HeadlessBrowserContextImpl(
+ builder->browser_, std::move(builder->options_)));
+}
+
+HeadlessWebContents::Builder
+HeadlessBrowserContextImpl::CreateWebContentsBuilder() {
+ DCHECK(browser_->BrowserMainThread()->BelongsToCurrentThread());
+ return HeadlessWebContents::Builder(this);
+}
+
+std::vector<HeadlessWebContents*>
+HeadlessBrowserContextImpl::GetAllWebContents() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ std::vector<HeadlessWebContents*> result;
+ result.reserve(web_contents_map_.size());
+
+ for (const auto& web_contents_pair : web_contents_map_) {
+ result.push_back(web_contents_pair.second.get());
+ }
+
+ return result;
+}
+
+void HeadlessBrowserContextImpl::Close() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ browser_->DestroyBrowserContext(this);
}
void HeadlessBrowserContextImpl::InitWhileIOAllowed() {
- // TODO(skyostil): Allow the embedder to override this.
- PathService::Get(base::DIR_EXE, &path_);
+ if (!context_options_->user_data_dir().empty()) {
+ path_ = context_options_->user_data_dir();
+ } else {
+ PathService::Get(base::DIR_EXE, &path_);
+ }
BrowserContext::Initialize(this, path_);
}
@@ -105,7 +166,7 @@ base::FilePath HeadlessBrowserContextImpl::GetPath() const {
}
bool HeadlessBrowserContextImpl::IsOffTheRecord() const {
- return false;
+ return context_options_->incognito_mode();
}
content::ResourceContext* HeadlessBrowserContextImpl::GetResourceContext() {
@@ -152,12 +213,12 @@ net::URLRequestContextGetter* HeadlessBrowserContextImpl::CreateRequestContext(
content::URLRequestInterceptorScopedVector request_interceptors) {
scoped_refptr<HeadlessURLRequestContextGetter> url_request_context_getter(
new HeadlessURLRequestContextGetter(
- content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::GetTaskRunnerForThread(
content::BrowserThread::IO),
- content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::GetTaskRunnerForThread(
content::BrowserThread::FILE),
- protocol_handlers, std::move(protocol_handlers_),
- std::move(request_interceptors), options()));
+ protocol_handlers, context_options_->TakeProtocolHandlers(),
+ std::move(request_interceptors), context_options_.get()));
resource_context_->set_url_request_context_getter(url_request_context_getter);
return url_request_context_getter.get();
}
@@ -183,13 +244,65 @@ HeadlessBrowserContextImpl::CreateMediaRequestContextForStoragePartition(
return nullptr;
}
-void HeadlessBrowserContextImpl::SetOptionsForTesting(
- HeadlessBrowser::Options* options) {
- options_ = options;
+HeadlessWebContents* HeadlessBrowserContextImpl::CreateWebContents(
+ HeadlessWebContents::Builder* builder) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ std::unique_ptr<HeadlessWebContentsImpl> headless_web_contents =
+ HeadlessWebContentsImpl::Create(builder,
+ browser()->window_tree_host()->window());
+
+ if (!headless_web_contents) {
+ return nullptr;
+ }
+
+ HeadlessWebContents* result = headless_web_contents.get();
+
+ RegisterWebContents(std::move(headless_web_contents));
+
+ return result;
+}
+
+void HeadlessBrowserContextImpl::RegisterWebContents(
+ std::unique_ptr<HeadlessWebContentsImpl> web_contents) {
+ DCHECK(web_contents);
+ web_contents_map_[web_contents->GetDevToolsAgentHostId()] =
+ std::move(web_contents);
+}
+
+void HeadlessBrowserContextImpl::DestroyWebContents(
+ HeadlessWebContentsImpl* web_contents) {
+ auto it = web_contents_map_.find(web_contents->GetDevToolsAgentHostId());
+ DCHECK(it != web_contents_map_.end());
+ web_contents_map_.erase(it);
+}
+
+HeadlessWebContents*
+HeadlessBrowserContextImpl::GetWebContentsForDevToolsAgentHostId(
+ const std::string& devtools_agent_host_id) {
+ auto find_it = web_contents_map_.find(devtools_agent_host_id);
+ if (find_it == web_contents_map_.end())
+ return nullptr;
+ return find_it->second.get();
+}
+
+HeadlessBrowserImpl* HeadlessBrowserContextImpl::browser() const {
+ return browser_;
+}
+
+const HeadlessBrowserContextOptions* HeadlessBrowserContextImpl::options()
+ const {
+ return context_options_.get();
+}
+
+const std::string& HeadlessBrowserContextImpl::Id() const {
+ return id_;
}
HeadlessBrowserContext::Builder::Builder(HeadlessBrowserImpl* browser)
- : browser_(browser) {}
+ : browser_(browser),
+ options_(new HeadlessBrowserContextOptions(browser->options())),
+ enable_http_and_https_if_mojo_used_(false) {}
HeadlessBrowserContext::Builder::~Builder() = default;
@@ -198,14 +311,106 @@ HeadlessBrowserContext::Builder::Builder(Builder&&) = default;
HeadlessBrowserContext::Builder&
HeadlessBrowserContext::Builder::SetProtocolHandlers(
ProtocolHandlerMap protocol_handlers) {
- protocol_handlers_ = std::move(protocol_handlers);
+ options_->protocol_handlers_ = std::move(protocol_handlers);
return *this;
}
-std::unique_ptr<HeadlessBrowserContext>
-HeadlessBrowserContext::Builder::Build() {
- return base::WrapUnique(new HeadlessBrowserContextImpl(
- std::move(protocol_handlers_), browser_->options()));
+HeadlessBrowserContext::Builder& HeadlessBrowserContext::Builder::SetUserAgent(
+ const std::string& user_agent) {
+ options_->user_agent_ = user_agent;
+ return *this;
+}
+
+HeadlessBrowserContext::Builder&
+HeadlessBrowserContext::Builder::SetProxyServer(
+ const net::HostPortPair& proxy_server) {
+ options_->proxy_server_ = proxy_server;
+ return *this;
+}
+
+HeadlessBrowserContext::Builder&
+HeadlessBrowserContext::Builder::SetHostResolverRules(
+ const std::string& host_resolver_rules) {
+ options_->host_resolver_rules_ = host_resolver_rules;
+ return *this;
}
+HeadlessBrowserContext::Builder& HeadlessBrowserContext::Builder::SetWindowSize(
+ const gfx::Size& window_size) {
+ options_->window_size_ = window_size;
+ return *this;
+}
+
+HeadlessBrowserContext::Builder&
+HeadlessBrowserContext::Builder::SetUserDataDir(
+ const base::FilePath& user_data_dir) {
+ options_->user_data_dir_ = user_data_dir;
+ return *this;
+}
+
+HeadlessBrowserContext::Builder&
+HeadlessBrowserContext::Builder::SetIncognitoMode(bool incognito_mode) {
+ options_->incognito_mode_ = incognito_mode;
+ return *this;
+}
+
+HeadlessBrowserContext::Builder&
+HeadlessBrowserContext::Builder::AddJsMojoBindings(
+ const std::string& mojom_name,
+ const std::string& js_bindings) {
+ mojo_bindings_.emplace_back(mojom_name, js_bindings);
+ return *this;
+}
+
+HeadlessBrowserContext::Builder&
+HeadlessBrowserContext::Builder::EnableUnsafeNetworkAccessWithMojoBindings(
+ bool enable_http_and_https_if_mojo_used) {
+ enable_http_and_https_if_mojo_used_ = enable_http_and_https_if_mojo_used;
+ return *this;
+}
+
+HeadlessBrowserContext::Builder&
+HeadlessBrowserContext::Builder::SetOverrideWebPreferencesCallback(
+ base::Callback<void(WebPreferences*)> callback) {
+ options_->override_web_preferences_callback_ = std::move(callback);
+ return *this;
+}
+
+HeadlessBrowserContext* HeadlessBrowserContext::Builder::Build() {
+ if (!mojo_bindings_.empty()) {
+ std::unique_ptr<InMemoryProtocolHandler> headless_mojom_protocol_handler(
+ new InMemoryProtocolHandler());
+ for (const MojoBindings& binding : mojo_bindings_) {
+ headless_mojom_protocol_handler->InsertResponse(
+ binding.mojom_name,
+ InMemoryProtocolHandler::Response(binding.js_bindings,
+ "application/javascript"));
+ }
+ DCHECK(options_->protocol_handlers_.find(kHeadlessMojomProtocol) ==
+ options_->protocol_handlers_.end());
+ options_->protocol_handlers_[kHeadlessMojomProtocol] =
+ std::move(headless_mojom_protocol_handler);
+
+ // Unless you know what you're doing it's unsafe to allow http/https for a
+ // context with mojo bindings.
+ if (!enable_http_and_https_if_mojo_used_) {
+ options_->protocol_handlers_[url::kHttpScheme] =
+ base::MakeUnique<BlackHoleProtocolHandler>();
+ options_->protocol_handlers_[url::kHttpsScheme] =
+ base::MakeUnique<BlackHoleProtocolHandler>();
+ }
+ }
+
+ return browser_->CreateBrowserContext(this);
+}
+
+HeadlessBrowserContext::Builder::MojoBindings::MojoBindings() {}
+
+HeadlessBrowserContext::Builder::MojoBindings::MojoBindings(
+ const std::string& mojom_name,
+ const std::string& js_bindings)
+ : mojom_name(mojom_name), js_bindings(js_bindings) {}
+
+HeadlessBrowserContext::Builder::MojoBindings::~MojoBindings() {}
+
} // namespace headless
diff --git a/chromium/headless/lib/browser/headless_browser_context_impl.h b/chromium/headless/lib/browser/headless_browser_context_impl.h
index fae8f8a8633..8d39dfcb44b 100644
--- a/chromium/headless/lib/browser/headless_browser_context_impl.h
+++ b/chromium/headless/lib/browser/headless_browser_context_impl.h
@@ -6,27 +6,44 @@
#define HEADLESS_LIB_BROWSER_HEADLESS_BROWSER_CONTEXT_IMPL_H_
#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
#include "base/files/file_path.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/resource_context.h"
+#include "headless/lib/browser/headless_browser_context_options.h"
#include "headless/lib/browser/headless_url_request_context_getter.h"
#include "headless/public/headless_browser.h"
#include "headless/public/headless_browser_context.h"
namespace headless {
+class HeadlessBrowserImpl;
class HeadlessResourceContext;
+class HeadlessWebContentsImpl;
class HeadlessBrowserContextImpl : public HeadlessBrowserContext,
public content::BrowserContext {
public:
- explicit HeadlessBrowserContextImpl(ProtocolHandlerMap protocol_handlers,
- HeadlessBrowser::Options* options);
~HeadlessBrowserContextImpl() override;
static HeadlessBrowserContextImpl* From(
HeadlessBrowserContext* browser_context);
+ static HeadlessBrowserContextImpl* From(
+ content::BrowserContext* browser_context);
+
+ static std::unique_ptr<HeadlessBrowserContextImpl> Create(
+ HeadlessBrowserContext::Builder* builder);
+
+ // HeadlessBrowserContext implementation:
+ HeadlessWebContents::Builder CreateWebContentsBuilder() override;
+ std::vector<HeadlessWebContents*> GetAllWebContents() override;
+ HeadlessWebContents* GetWebContentsForDevToolsAgentHostId(
+ const std::string& devtools_agent_host_id) override;
+ void Close() override;
+ const std::string& Id() const override;
// BrowserContext implementation:
std::unique_ptr<content::ZoomLevelDelegate> CreateZoomLevelDelegate(
@@ -54,19 +71,35 @@ class HeadlessBrowserContextImpl : public HeadlessBrowserContext,
const base::FilePath& partition_path,
bool in_memory) override;
- HeadlessBrowser::Options* options() const { return options_; }
- void SetOptionsForTesting(HeadlessBrowser::Options* options);
+ HeadlessWebContents* CreateWebContents(HeadlessWebContents::Builder* builder);
+ // Register web contents which were created not through Headless API
+ // (calling window.open() is a best example for this).
+ void RegisterWebContents(
+ std::unique_ptr<HeadlessWebContentsImpl> web_contents);
+ void DestroyWebContents(HeadlessWebContentsImpl* web_contents);
+
+ HeadlessBrowserImpl* browser() const;
+ const HeadlessBrowserContextOptions* options() const;
private:
+ HeadlessBrowserContextImpl(
+ HeadlessBrowserImpl* browser,
+ std::unique_ptr<HeadlessBrowserContextOptions> context_options);
+
// Performs initialization of the HeadlessBrowserContextImpl while IO is still
// allowed on the current thread.
void InitWhileIOAllowed();
- ProtocolHandlerMap protocol_handlers_;
- HeadlessBrowser::Options* options_; // Not owned.
+ HeadlessBrowserImpl* browser_; // Not owned.
+ std::unique_ptr<HeadlessBrowserContextOptions> context_options_;
std::unique_ptr<HeadlessResourceContext> resource_context_;
base::FilePath path_;
+ std::unordered_map<std::string, std::unique_ptr<HeadlessWebContents>>
+ web_contents_map_;
+
+ std::string id_;
+
DISALLOW_COPY_AND_ASSIGN(HeadlessBrowserContextImpl);
};
diff --git a/chromium/headless/lib/browser/headless_browser_context_options.cc b/chromium/headless/lib/browser/headless_browser_context_options.cc
new file mode 100644
index 00000000000..13faa83889e
--- /dev/null
+++ b/chromium/headless/lib/browser/headless_browser_context_options.cc
@@ -0,0 +1,78 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/lib/browser/headless_browser_context_options.h"
+
+#include <string>
+#include <utility>
+
+namespace headless {
+
+namespace {
+
+template <class T>
+const T& ReturnOverriddenValue(const base::Optional<T>& value,
+ const T& default_value) {
+ if (value) {
+ return *value;
+ } else {
+ return default_value;
+ }
+}
+
+} // namespace
+
+HeadlessBrowserContextOptions::HeadlessBrowserContextOptions(
+ HeadlessBrowserContextOptions&& options) = default;
+
+HeadlessBrowserContextOptions::~HeadlessBrowserContextOptions() = default;
+
+HeadlessBrowserContextOptions& HeadlessBrowserContextOptions::operator=(
+ HeadlessBrowserContextOptions&& options) = default;
+
+HeadlessBrowserContextOptions::HeadlessBrowserContextOptions(
+ HeadlessBrowser::Options* options)
+ : browser_options_(options) {}
+
+const std::string& HeadlessBrowserContextOptions::user_agent() const {
+ return ReturnOverriddenValue(user_agent_, browser_options_->user_agent);
+}
+
+const net::HostPortPair& HeadlessBrowserContextOptions::proxy_server() const {
+ return ReturnOverriddenValue(proxy_server_, browser_options_->proxy_server);
+}
+
+const std::string& HeadlessBrowserContextOptions::host_resolver_rules() const {
+ return ReturnOverriddenValue(host_resolver_rules_,
+ browser_options_->host_resolver_rules);
+}
+
+const gfx::Size& HeadlessBrowserContextOptions::window_size() const {
+ return ReturnOverriddenValue(window_size_, browser_options_->window_size);
+}
+
+const base::FilePath& HeadlessBrowserContextOptions::user_data_dir() const {
+ return ReturnOverriddenValue(user_data_dir_, browser_options_->user_data_dir);
+}
+
+bool HeadlessBrowserContextOptions::incognito_mode() const {
+ return ReturnOverriddenValue(incognito_mode_,
+ browser_options_->incognito_mode);
+}
+
+const ProtocolHandlerMap& HeadlessBrowserContextOptions::protocol_handlers()
+ const {
+ return protocol_handlers_;
+}
+
+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
new file mode 100644
index 00000000000..4c6601757f4
--- /dev/null
+++ b/chromium/headless/lib/browser/headless_browser_context_options.h
@@ -0,0 +1,78 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_LIB_BROWSER_HEADLESS_BROWSER_CONTEXT_OPTIONS_H_
+#define HEADLESS_LIB_BROWSER_HEADLESS_BROWSER_CONTEXT_OPTIONS_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/optional.h"
+#include "headless/public/headless_browser.h"
+#include "headless/public/headless_browser_context.h"
+
+namespace headless {
+
+// Represents options which can be customized for a given BrowserContext.
+// Provides a fallback to default browser-side options when an option
+// is not set for a particular BrowserContext.
+class HeadlessBrowserContextOptions {
+ public:
+ HeadlessBrowserContextOptions(HeadlessBrowserContextOptions&& options);
+ ~HeadlessBrowserContextOptions();
+
+ HeadlessBrowserContextOptions& operator=(
+ HeadlessBrowserContextOptions&& options);
+
+ const std::string& user_agent() const;
+
+ // See HeadlessBrowser::Options::proxy_server.
+ const net::HostPortPair& proxy_server() const;
+
+ // See HeadlessBrowser::Options::host_resolver_rules.
+ const std::string& host_resolver_rules() const;
+
+ const gfx::Size& window_size() const;
+
+ // See HeadlessBrowser::Options::user_data_dir.
+ const base::FilePath& user_data_dir() const;
+
+ // Set HeadlessBrowser::Options::incognito_mode.
+ bool incognito_mode() const;
+
+ // Custom network protocol handlers. These can be used to override URL
+ // fetching for different network schemes.
+ const ProtocolHandlerMap& protocol_handlers() const;
+ // Since ProtocolHandlerMap is move-only, this method takes ownership of them.
+ ProtocolHandlerMap TakeProtocolHandlers();
+
+ // Callback that is invoked to override WebPreferences for RenderViews
+ // created within this HeadlessBrowserContext.
+ const base::Callback<void(WebPreferences*)>&
+ override_web_preferences_callback() const;
+
+ private:
+ friend class HeadlessBrowserContext::Builder;
+
+ explicit HeadlessBrowserContextOptions(HeadlessBrowser::Options*);
+
+ HeadlessBrowser::Options* browser_options_;
+
+ 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_;
+
+ ProtocolHandlerMap protocol_handlers_;
+ base::Callback<void(WebPreferences*)> override_web_preferences_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(HeadlessBrowserContextOptions);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_LIB_BROWSER_HEADLESS_BROWSER_CONTEXT_OPTIONS_H_
diff --git a/chromium/headless/lib/browser/headless_browser_impl.cc b/chromium/headless/lib/browser/headless_browser_impl.cc
index a3eb1ba3740..98fcac84ed3 100644
--- a/chromium/headless/lib/browser/headless_browser_impl.cc
+++ b/chromium/headless/lib/browser/headless_browser_impl.cc
@@ -4,6 +4,8 @@
#include "headless/lib/browser/headless_browser_impl.h"
+#include <string>
+#include <utility>
#include <vector>
#include "base/command_line.h"
@@ -49,73 +51,63 @@ HeadlessBrowserImpl::HeadlessBrowserImpl(
HeadlessBrowser::Options options)
: on_start_callback_(on_start_callback),
options_(std::move(options)),
- browser_main_parts_(nullptr) {
-}
+ browser_main_parts_(nullptr),
+ weak_ptr_factory_(this) {}
HeadlessBrowserImpl::~HeadlessBrowserImpl() {}
-HeadlessWebContents::Builder HeadlessBrowserImpl::CreateWebContentsBuilder() {
- DCHECK(BrowserMainThread()->BelongsToCurrentThread());
- return HeadlessWebContents::Builder(this);
-}
-
HeadlessBrowserContext::Builder
HeadlessBrowserImpl::CreateBrowserContextBuilder() {
- DCHECK(BrowserMainThread()->BelongsToCurrentThread());
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return HeadlessBrowserContext::Builder(this);
}
-HeadlessWebContents* HeadlessBrowserImpl::CreateWebContents(
- HeadlessWebContents::Builder* builder) {
- DCHECK(BrowserMainThread()->BelongsToCurrentThread());
- std::unique_ptr<HeadlessWebContentsImpl> headless_web_contents =
- HeadlessWebContentsImpl::Create(builder, window_tree_host_->window(),
- this);
- if (!headless_web_contents)
- return nullptr;
- return RegisterWebContents(std::move(headless_web_contents));
+scoped_refptr<base::SingleThreadTaskRunner>
+HeadlessBrowserImpl::BrowserFileThread() const {
+ return content::BrowserThread::GetTaskRunnerForThread(
+ content::BrowserThread::FILE);
}
-HeadlessWebContents* HeadlessBrowserImpl::CreateWebContents(
- const GURL& initial_url,
- const gfx::Size& size) {
- return CreateWebContentsBuilder()
- .SetInitialURL(initial_url)
- .SetWindowSize(size)
- .Build();
+scoped_refptr<base::SingleThreadTaskRunner>
+HeadlessBrowserImpl::BrowserIOThread() const {
+ return content::BrowserThread::GetTaskRunnerForThread(
+ content::BrowserThread::IO);
}
scoped_refptr<base::SingleThreadTaskRunner>
HeadlessBrowserImpl::BrowserMainThread() const {
- return content::BrowserThread::GetMessageLoopProxyForThread(
+ return content::BrowserThread::GetTaskRunnerForThread(
content::BrowserThread::UI);
}
-scoped_refptr<base::SingleThreadTaskRunner>
-HeadlessBrowserImpl::BrowserFileThread() const {
- return content::BrowserThread::GetMessageLoopProxyForThread(
- content::BrowserThread::FILE);
-}
-
void HeadlessBrowserImpl::Shutdown() {
- DCHECK(BrowserMainThread()->BelongsToCurrentThread());
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ weak_ptr_factory_.InvalidateWeakPtrs();
+
+ // Destroy all browser contexts.
+ browser_contexts_.clear();
+
BrowserMainThread()->PostTask(FROM_HERE,
base::MessageLoop::QuitWhenIdleClosure());
}
-std::vector<HeadlessWebContents*> HeadlessBrowserImpl::GetAllWebContents() {
- std::vector<HeadlessWebContents*> result;
- result.reserve(web_contents_.size());
+std::vector<HeadlessBrowserContext*>
+HeadlessBrowserImpl::GetAllBrowserContexts() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ std::vector<HeadlessBrowserContext*> result;
+ result.reserve(browser_contexts_.size());
- for (const auto& web_contents_pair : web_contents_) {
- result.push_back(web_contents_pair.first);
+ for (const auto& browser_context_pair : browser_contexts_) {
+ result.push_back(browser_context_pair.second.get());
}
return result;
}
HeadlessBrowserMainParts* HeadlessBrowserImpl::browser_main_parts() const {
- DCHECK(BrowserMainThread()->BelongsToCurrentThread());
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return browser_main_parts_;
}
@@ -127,10 +119,8 @@ void HeadlessBrowserImpl::set_browser_main_parts(
void HeadlessBrowserImpl::RunOnStartCallback() {
DCHECK(aura::Env::GetInstance());
- // TODO(eseckler): allow configuration of window (viewport) size by embedder.
- const gfx::Size kDefaultSize(800, 600);
window_tree_host_.reset(
- aura::WindowTreeHost::Create(gfx::Rect(kDefaultSize)));
+ aura::WindowTreeHost::Create(gfx::Rect(options()->window_size)));
window_tree_host_->InitHost();
window_tree_client_.reset(
@@ -140,26 +130,57 @@ void HeadlessBrowserImpl::RunOnStartCallback() {
on_start_callback_ = base::Callback<void(HeadlessBrowser*)>();
}
-HeadlessWebContentsImpl* HeadlessBrowserImpl::RegisterWebContents(
- std::unique_ptr<HeadlessWebContentsImpl> web_contents) {
- DCHECK(web_contents);
- HeadlessWebContentsImpl* unowned_web_contents = web_contents.get();
- web_contents_[unowned_web_contents] = std::move(web_contents);
- return unowned_web_contents;
+HeadlessBrowserContext* HeadlessBrowserImpl::CreateBrowserContext(
+ HeadlessBrowserContext::Builder* builder) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ std::unique_ptr<HeadlessBrowserContextImpl> browser_context =
+ HeadlessBrowserContextImpl::Create(builder);
+
+ if (!browser_context) {
+ return nullptr;
+ }
+
+ HeadlessBrowserContext* result = browser_context.get();
+
+ browser_contexts_[browser_context->Id()] = std::move(browser_context);
+
+ return result;
}
-void HeadlessBrowserImpl::DestroyWebContents(
- HeadlessWebContentsImpl* web_contents) {
- auto it = web_contents_.find(web_contents);
- DCHECK(it != web_contents_.end());
- web_contents_.erase(it);
+void HeadlessBrowserImpl::DestroyBrowserContext(
+ HeadlessBrowserContextImpl* browser_context) {
+ auto it = browser_contexts_.find(browser_context->Id());
+ DCHECK(it != browser_contexts_.end());
+ browser_contexts_.erase(it);
}
-void HeadlessBrowserImpl::SetOptionsForTesting(
- HeadlessBrowser::Options options) {
- options_ = std::move(options);
- browser_main_parts()->default_browser_context()->SetOptionsForTesting(
- &options_);
+base::WeakPtr<HeadlessBrowserImpl> HeadlessBrowserImpl::GetWeakPtr() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ 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()) {
+ HeadlessWebContents* web_contents =
+ context->GetWebContentsForDevToolsAgentHostId(devtools_agent_host_id);
+ if (web_contents)
+ return web_contents;
+ }
+ return nullptr;
+}
+
+HeadlessBrowserContext* HeadlessBrowserImpl::GetBrowserContextForId(
+ const std::string& id) {
+ auto find_it = browser_contexts_.find(id);
+ if (find_it == browser_contexts_.end())
+ return nullptr;
+ return find_it->second.get();
}
void RunChildProcessIfNeeded(int argc, const char** argv) {
diff --git a/chromium/headless/lib/browser/headless_browser_impl.h b/chromium/headless/lib/browser/headless_browser_impl.h
index 68dc676e5dd..a31391816c0 100644
--- a/chromium/headless/lib/browser/headless_browser_impl.h
+++ b/chromium/headless/lib/browser/headless_browser_impl.h
@@ -8,9 +8,12 @@
#include "headless/public/headless_browser.h"
#include <memory>
+#include <string>
#include <unordered_map>
#include <vector>
+#include "base/memory/weak_ptr.h"
+#include "headless/lib/browser/headless_devtools_manager_delegate.h"
#include "headless/lib/browser/headless_web_contents_impl.h"
namespace aura {
@@ -23,7 +26,7 @@ class WindowTreeClient;
namespace headless {
-class HeadlessBrowserContext;
+class HeadlessBrowserContextImpl;
class HeadlessBrowserMainParts;
class HeadlessBrowserImpl : public HeadlessBrowser {
@@ -34,18 +37,20 @@ class HeadlessBrowserImpl : public HeadlessBrowser {
~HeadlessBrowserImpl() override;
// HeadlessBrowser implementation:
- HeadlessWebContents::Builder CreateWebContentsBuilder() override;
HeadlessBrowserContext::Builder CreateBrowserContextBuilder() override;
- HeadlessWebContents* CreateWebContents(const GURL& initial_url,
- const gfx::Size& size) override;
- scoped_refptr<base::SingleThreadTaskRunner> BrowserMainThread()
- const override;
scoped_refptr<base::SingleThreadTaskRunner> BrowserFileThread()
const override;
+ scoped_refptr<base::SingleThreadTaskRunner> BrowserIOThread() const override;
+ scoped_refptr<base::SingleThreadTaskRunner> BrowserMainThread()
+ const override;
void Shutdown() override;
- std::vector<HeadlessWebContents*> GetAllWebContents() override;
+ std::vector<HeadlessBrowserContext*> GetAllBrowserContexts() override;
+ HeadlessWebContents* GetWebContentsForDevToolsAgentHostId(
+ const std::string& devtools_agent_host_id) override;
+ HeadlessBrowserContext* GetBrowserContextForId(
+ const std::string& id) override;
void set_browser_main_parts(HeadlessBrowserMainParts* browser_main_parts);
HeadlessBrowserMainParts* browser_main_parts() const;
@@ -54,27 +59,31 @@ class HeadlessBrowserImpl : public HeadlessBrowser {
HeadlessBrowser::Options* options() { return &options_; }
- HeadlessWebContents* CreateWebContents(HeadlessWebContents::Builder* builder);
- HeadlessWebContentsImpl* RegisterWebContents(
- std::unique_ptr<HeadlessWebContentsImpl> web_contents);
+ HeadlessBrowserContext* CreateBrowserContext(
+ HeadlessBrowserContext::Builder* builder);
+ // Close given |browser_context| and delete it
+ // (all web contents associated with it go away too).
+ void DestroyBrowserContext(HeadlessBrowserContextImpl* browser_context);
- // Close given |web_contents| and delete it.
- void DestroyWebContents(HeadlessWebContentsImpl* web_contents);
+ base::WeakPtr<HeadlessBrowserImpl> GetWeakPtr();
- // Customize the options used by this headless browser instance. Note that
- // options which take effect before the message loop has been started (e.g.,
- // custom message pumps) cannot be set via this method.
- void SetOptionsForTesting(HeadlessBrowser::Options options);
+ aura::WindowTreeHost* window_tree_host() const;
protected:
base::Callback<void(HeadlessBrowser*)> on_start_callback_;
HeadlessBrowser::Options options_;
HeadlessBrowserMainParts* browser_main_parts_; // Not owned.
+
+ // 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::WindowTreeClient> window_tree_client_;
- std::unordered_map<HeadlessWebContents*, std::unique_ptr<HeadlessWebContents>>
- web_contents_;
+ std::unordered_map<std::string, std::unique_ptr<HeadlessBrowserContextImpl>>
+ browser_contexts_;
+
+ base::WeakPtrFactory<HeadlessBrowserImpl> weak_ptr_factory_;
private:
DISALLOW_COPY_AND_ASSIGN(HeadlessBrowserImpl);
diff --git a/chromium/headless/lib/browser/headless_browser_main_parts.cc b/chromium/headless/lib/browser/headless_browser_main_parts.cc
index c970b790739..b219480b6f7 100644
--- a/chromium/headless/lib/browser/headless_browser_main_parts.cc
+++ b/chromium/headless/lib/browser/headless_browser_main_parts.cc
@@ -4,7 +4,6 @@
#include "headless/lib/browser/headless_browser_main_parts.h"
-#include "components/devtools_http_handler/devtools_http_handler.h"
#include "headless/lib/browser/headless_browser_context_impl.h"
#include "headless/lib/browser/headless_browser_impl.h"
#include "headless/lib/browser/headless_devtools.h"
@@ -16,8 +15,8 @@ namespace headless {
namespace {
-void PlatformInitialize() {
- HeadlessScreen* screen = HeadlessScreen::Create(gfx::Size());
+void PlatformInitialize(const gfx::Size& screen_size) {
+ HeadlessScreen* screen = HeadlessScreen::Create(screen_size);
display::Screen::SetScreenInstance(screen);
}
@@ -27,29 +26,25 @@ void PlatformExit() {
} // namespace
HeadlessBrowserMainParts::HeadlessBrowserMainParts(HeadlessBrowserImpl* browser)
- : browser_(browser) {}
+ : browser_(browser)
+ , devtools_http_handler_started_(false) {}
HeadlessBrowserMainParts::~HeadlessBrowserMainParts() {}
void HeadlessBrowserMainParts::PreMainMessageLoopRun() {
- browser_context_.reset(new HeadlessBrowserContextImpl(ProtocolHandlerMap(),
- browser_->options()));
if (browser_->options()->devtools_endpoint.address().IsValid()) {
- devtools_http_handler_ =
- CreateLocalDevToolsHttpHandler(browser_context_.get());
+ StartLocalDevToolsHttpHandler(browser_->options());
+ devtools_http_handler_started_ = true;
}
- PlatformInitialize();
+ PlatformInitialize(browser_->options()->window_size);
}
void HeadlessBrowserMainParts::PostMainMessageLoopRun() {
- browser_context_.reset();
- devtools_http_handler_.reset();
+ if (devtools_http_handler_started_) {
+ StopLocalDevToolsHttpHandler();
+ devtools_http_handler_started_ = false;
+ }
PlatformExit();
}
-HeadlessBrowserContextImpl* HeadlessBrowserMainParts::default_browser_context()
- const {
- return browser_context_.get();
-}
-
} // namespace headless
diff --git a/chromium/headless/lib/browser/headless_browser_main_parts.h b/chromium/headless/lib/browser/headless_browser_main_parts.h
index b0e30c397b8..97bc9e9e912 100644
--- a/chromium/headless/lib/browser/headless_browser_main_parts.h
+++ b/chromium/headless/lib/browser/headless_browser_main_parts.h
@@ -10,10 +10,6 @@
#include "content/public/browser/browser_main_parts.h"
#include "headless/public/headless_browser.h"
-namespace devtools_http_handler {
-class DevToolsHttpHandler;
-}
-
namespace headless {
class HeadlessBrowserContextImpl;
@@ -28,13 +24,10 @@ class HeadlessBrowserMainParts : public content::BrowserMainParts {
void PreMainMessageLoopRun() override;
void PostMainMessageLoopRun() override;
- HeadlessBrowserContextImpl* default_browser_context() const;
-
private:
HeadlessBrowserImpl* browser_; // Not owned.
- std::unique_ptr<HeadlessBrowserContextImpl> browser_context_;
- std::unique_ptr<devtools_http_handler::DevToolsHttpHandler>
- devtools_http_handler_;
+
+ bool devtools_http_handler_started_;
DISALLOW_COPY_AND_ASSIGN(HeadlessBrowserMainParts);
};
diff --git a/chromium/headless/lib/browser/headless_content_browser_client.cc b/chromium/headless/lib/browser/headless_content_browser_client.cc
index a56a1fef4d9..4b59e918e5c 100644
--- a/chromium/headless/lib/browser/headless_content_browser_client.cc
+++ b/chromium/headless/lib/browser/headless_content_browser_client.cc
@@ -6,10 +6,16 @@
#include <memory>
+#include "base/callback.h"
#include "base/memory/ptr_util.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 "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"
namespace headless {
@@ -22,9 +28,25 @@ HeadlessContentBrowserClient::~HeadlessContentBrowserClient() {}
content::BrowserMainParts* HeadlessContentBrowserClient::CreateBrowserMainParts(
const content::MainFunctionParams&) {
std::unique_ptr<HeadlessBrowserMainParts> browser_main_parts =
- base::WrapUnique(new HeadlessBrowserMainParts(browser_));
+ base::MakeUnique<HeadlessBrowserMainParts>(browser_);
browser_->set_browser_main_parts(browser_main_parts.get());
return browser_main_parts.release();
}
+void HeadlessContentBrowserClient::OverrideWebkitPrefs(
+ content::RenderViewHost* render_view_host,
+ content::WebPreferences* prefs) {
+ auto browser_context = HeadlessBrowserContextImpl::From(
+ render_view_host->GetProcess()->GetBrowserContext());
+ const base::Callback<void(headless::WebPreferences*)>& callback =
+ browser_context->options()->override_web_preferences_callback();
+ if (callback)
+ callback.Run(prefs);
+}
+
+content::DevToolsManagerDelegate*
+HeadlessContentBrowserClient::GetDevToolsManagerDelegate() {
+ return new HeadlessDevToolsManagerDelegate(browser_->GetWeakPtr());
+}
+
} // 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 16d9ef300ae..0bd58b12246 100644
--- a/chromium/headless/lib/browser/headless_content_browser_client.h
+++ b/chromium/headless/lib/browser/headless_content_browser_client.h
@@ -11,6 +11,7 @@ namespace headless {
class HeadlessBrowserImpl;
class HeadlessBrowserMainParts;
+class HeadlessDevToolsManagerDelegate;
class HeadlessContentBrowserClient : public content::ContentBrowserClient {
public:
@@ -20,6 +21,9 @@ class HeadlessContentBrowserClient : public content::ContentBrowserClient {
// content::ContentBrowserClient implementation:
content::BrowserMainParts* CreateBrowserMainParts(
const content::MainFunctionParams&) override;
+ void OverrideWebkitPrefs(content::RenderViewHost* render_view_host,
+ content::WebPreferences* prefs) override;
+ content::DevToolsManagerDelegate* GetDevToolsManagerDelegate() 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 626a29de89e..ea97afcc96a 100644
--- a/chromium/headless/lib/browser/headless_devtools.cc
+++ b/chromium/headless/lib/browser/headless_devtools.cc
@@ -4,101 +4,71 @@
#include "headless/lib/browser/headless_devtools.h"
+#include <string>
+#include <utility>
+
#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
-#include "components/devtools_http_handler/devtools_http_handler.h"
-#include "components/devtools_http_handler/devtools_http_handler_delegate.h"
+#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/devtools_frontend_host.h"
+#include "content/public/browser/devtools_socket_factory.h"
#include "content/public/browser/navigation_entry.h"
#include "headless/grit/headless_lib_resources.h"
-#include "headless/lib/browser/headless_browser_context_impl.h"
+#include "headless/public/headless_browser.h"
#include "net/base/net_errors.h"
+#include "net/log/net_log_source.h"
#include "net/socket/tcp_server_socket.h"
#include "ui/base/resource/resource_bundle.h"
-using devtools_http_handler::DevToolsHttpHandler;
-
namespace headless {
namespace {
const int kBackLog = 10;
-class TCPServerSocketFactory : public DevToolsHttpHandler::ServerSocketFactory {
+class TCPServerSocketFactory : public content::DevToolsSocketFactory {
public:
- TCPServerSocketFactory(const net::IPEndPoint& endpoint)
+ explicit TCPServerSocketFactory(const net::IPEndPoint& endpoint)
: endpoint_(endpoint) {
DCHECK(endpoint_.address().IsValid());
}
private:
- // DevToolsHttpHandler::ServerSocketFactory implementation:
+ // content::DevToolsSocketFactory implementation:
std::unique_ptr<net::ServerSocket> CreateForHttpServer() override {
std::unique_ptr<net::ServerSocket> socket(
- new net::TCPServerSocket(nullptr, net::NetLog::Source()));
+ new net::TCPServerSocket(nullptr, net::NetLogSource()));
if (socket->Listen(endpoint_, kBackLog) != net::OK)
return std::unique_ptr<net::ServerSocket>();
return socket;
}
+ std::unique_ptr<net::ServerSocket> CreateForTethering(
+ std::string* out_name) override {
+ return nullptr;
+ }
+
net::IPEndPoint endpoint_;
DISALLOW_COPY_AND_ASSIGN(TCPServerSocketFactory);
};
-class HeadlessDevToolsDelegate
- : public devtools_http_handler::DevToolsHttpHandlerDelegate {
- public:
- HeadlessDevToolsDelegate();
- ~HeadlessDevToolsDelegate() override;
-
- // devtools_http_handler::DevToolsHttpHandlerDelegate implementation:
- std::string GetDiscoveryPageHTML() override;
- std::string GetFrontendResource(const std::string& path) override;
- std::string GetPageThumbnailData(const GURL& url) override;
- content::DevToolsExternalAgentProxyDelegate* HandleWebSocketConnection(
- const std::string& path) override;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(HeadlessDevToolsDelegate);
-};
-
-HeadlessDevToolsDelegate::HeadlessDevToolsDelegate() {}
-
-HeadlessDevToolsDelegate::~HeadlessDevToolsDelegate() {}
-
-std::string HeadlessDevToolsDelegate::GetDiscoveryPageHTML() {
- return ResourceBundle::GetSharedInstance().GetRawDataResource(
- IDR_HEADLESS_LIB_DEVTOOLS_DISCOVERY_PAGE).as_string();
-}
-
-std::string HeadlessDevToolsDelegate::GetFrontendResource(
- const std::string& path) {
- return content::DevToolsFrontendHost::GetFrontendResource(path).as_string();
-}
-
-std::string HeadlessDevToolsDelegate::GetPageThumbnailData(const GURL& url) {
- return std::string();
-}
-
-content::DevToolsExternalAgentProxyDelegate*
-HeadlessDevToolsDelegate::HandleWebSocketConnection(const std::string& path) {
- return nullptr;
-}
-
} // namespace
-std::unique_ptr<DevToolsHttpHandler> CreateLocalDevToolsHttpHandler(
- HeadlessBrowserContextImpl* browser_context) {
- const net::IPEndPoint& endpoint =
- browser_context->options()->devtools_endpoint;
- std::unique_ptr<DevToolsHttpHandler::ServerSocketFactory> socket_factory(
+void StartLocalDevToolsHttpHandler(
+ HeadlessBrowser::Options* options) {
+ const net::IPEndPoint& endpoint = options->devtools_endpoint;
+ std::unique_ptr<content::DevToolsSocketFactory> socket_factory(
new TCPServerSocketFactory(endpoint));
- return base::WrapUnique(new DevToolsHttpHandler(
- std::move(socket_factory), std::string(), new HeadlessDevToolsDelegate(),
- browser_context->GetPath(), base::FilePath(), std::string(),
- browser_context->options()->user_agent));
+ 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);
+}
+
+void StopLocalDevToolsHttpHandler() {
+ content::DevToolsAgentHost::StopRemoteDebuggingServer();
}
} // namespace headless
diff --git a/chromium/headless/lib/browser/headless_devtools.h b/chromium/headless/lib/browser/headless_devtools.h
index b2b4a6aff80..eccdd088806 100644
--- a/chromium/headless/lib/browser/headless_devtools.h
+++ b/chromium/headless/lib/browser/headless_devtools.h
@@ -7,18 +7,15 @@
#include <memory>
-namespace devtools_http_handler {
-class DevToolsHttpHandler;
-}
+#include "headless/public/headless_browser.h"
namespace headless {
-class HeadlessBrowserContextImpl;
// Starts a DevTools HTTP handler on the loopback interface on the port
// configured by HeadlessBrowser::Options.
-std::unique_ptr<devtools_http_handler::DevToolsHttpHandler>
-CreateLocalDevToolsHttpHandler(HeadlessBrowserContextImpl* browser_context);
+void StartLocalDevToolsHttpHandler(HeadlessBrowser::Options* options);
+void StopLocalDevToolsHttpHandler();
-} // namespace content
+} // namespace headless
#endif // HEADLESS_LIB_BROWSER_HEADLESS_DEVTOOLS_H_
diff --git a/chromium/headless/lib/browser/headless_devtools_client_impl.cc b/chromium/headless/lib/browser/headless_devtools_client_impl.cc
index 41aa4fc6994..a669e1e5b1d 100644
--- a/chromium/headless/lib/browser/headless_devtools_client_impl.cc
+++ b/chromium/headless/lib/browser/headless_devtools_client_impl.cc
@@ -6,10 +6,12 @@
#include <memory>
+#include "base/bind.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "base/values.h"
+#include "content/public/browser/browser_thread.h"
#include "content/public/browser/devtools_agent_host.h"
namespace headless {
@@ -33,6 +35,7 @@ HeadlessDevToolsClientImpl::HeadlessDevToolsClientImpl()
accessibility_domain_(this),
animation_domain_(this),
application_cache_domain_(this),
+ browser_domain_(this),
cache_storage_domain_(this),
console_domain_(this),
css_domain_(this),
@@ -49,6 +52,7 @@ HeadlessDevToolsClientImpl::HeadlessDevToolsClientImpl()
inspector_domain_(this),
io_domain_(this),
layer_tree_domain_(this),
+ log_domain_(this),
memory_domain_(this),
network_domain_(this),
page_domain_(this),
@@ -58,7 +62,10 @@ HeadlessDevToolsClientImpl::HeadlessDevToolsClientImpl()
security_domain_(this),
service_worker_domain_(this),
tracing_domain_(this),
- worker_domain_(this) {}
+ worker_domain_(this),
+ browser_main_thread_(content::BrowserThread::GetTaskRunnerForThread(
+ content::BrowserThread::UI)),
+ weak_ptr_factory_(this) {}
HeadlessDevToolsClientImpl::~HeadlessDevToolsClientImpl() {}
@@ -88,8 +95,10 @@ void HeadlessDevToolsClientImpl::DispatchProtocolMessage(
NOTREACHED() << "Badly formed reply";
return;
}
- if (!DispatchMessageReply(*message_dict) && !DispatchEvent(*message_dict))
+ if (!DispatchMessageReply(*message_dict) &&
+ !DispatchEvent(std::move(message), *message_dict)) {
DLOG(ERROR) << "Unhandled protocol message: " << json_message;
+ }
}
bool HeadlessDevToolsClientImpl::DispatchMessageReply(
@@ -118,11 +127,12 @@ bool HeadlessDevToolsClientImpl::DispatchMessageReply(
}
bool HeadlessDevToolsClientImpl::DispatchEvent(
+ std::unique_ptr<base::Value> owning_message,
const base::DictionaryValue& message_dict) {
std::string method;
if (!message_dict.GetString("method", &method))
return false;
- auto it = event_handlers_.find(method);
+ EventHandlerMap::const_iterator it = event_handlers_.find(method);
if (it == event_handlers_.end()) {
NOTREACHED() << "Unknown event: " << method;
return false;
@@ -133,11 +143,24 @@ bool HeadlessDevToolsClientImpl::DispatchEvent(
NOTREACHED() << "Badly formed event parameters";
return false;
}
- it->second.Run(*result_dict);
+ // DevTools assumes event handling is async so we must post a task here or
+ // we risk breaking things.
+ browser_main_thread_->PostTask(
+ FROM_HERE, base::Bind(&HeadlessDevToolsClientImpl::DispatchEventTask,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(std::move(owning_message)),
+ &it->second, result_dict));
}
return true;
}
+void HeadlessDevToolsClientImpl::DispatchEventTask(
+ std::unique_ptr<base::Value> owning_message,
+ const EventHandler* event_handler,
+ const base::DictionaryValue* result_dict) {
+ event_handler->Run(*result_dict);
+}
+
void HeadlessDevToolsClientImpl::AgentHostClosed(
content::DevToolsAgentHost* agent_host,
bool replaced_with_another_client) {
@@ -158,6 +181,10 @@ application_cache::Domain* HeadlessDevToolsClientImpl::GetApplicationCache() {
return &application_cache_domain_;
}
+browser::Domain* HeadlessDevToolsClientImpl::GetBrowser() {
+ return &browser_domain_;
+}
+
cache_storage::Domain* HeadlessDevToolsClientImpl::GetCacheStorage() {
return &cache_storage_domain_;
}
@@ -222,6 +249,10 @@ layer_tree::Domain* HeadlessDevToolsClientImpl::GetLayerTree() {
return &layer_tree_domain_;
}
+log::Domain* HeadlessDevToolsClientImpl::GetLog() {
+ return &log_domain_;
+}
+
memory::Domain* HeadlessDevToolsClientImpl::GetMemory() {
return &memory_domain_;
}
diff --git a/chromium/headless/lib/browser/headless_devtools_client_impl.h b/chromium/headless/lib/browser/headless_devtools_client_impl.h
index c7dbb52db45..619825d28ba 100644
--- a/chromium/headless/lib/browser/headless_devtools_client_impl.h
+++ b/chromium/headless/lib/browser/headless_devtools_client_impl.h
@@ -7,10 +7,13 @@
#include <unordered_map>
+#include "base/memory/weak_ptr.h"
+#include "base/single_thread_task_runner.h"
#include "content/public/browser/devtools_agent_host_client.h"
#include "headless/public/domains/accessibility.h"
#include "headless/public/domains/animation.h"
#include "headless/public/domains/application_cache.h"
+#include "headless/public/domains/browser.h"
#include "headless/public/domains/cache_storage.h"
#include "headless/public/domains/console.h"
#include "headless/public/domains/css.h"
@@ -27,6 +30,7 @@
#include "headless/public/domains/inspector.h"
#include "headless/public/domains/io.h"
#include "headless/public/domains/layer_tree.h"
+#include "headless/public/domains/log.h"
#include "headless/public/domains/memory.h"
#include "headless/public/domains/network.h"
#include "headless/public/domains/page.h"
@@ -63,6 +67,7 @@ class HeadlessDevToolsClientImpl : public HeadlessDevToolsClient,
accessibility::Domain* GetAccessibility() override;
animation::Domain* GetAnimation() override;
application_cache::Domain* GetApplicationCache() override;
+ browser::Domain* GetBrowser() override;
cache_storage::Domain* GetCacheStorage() override;
console::Domain* GetConsole() override;
css::Domain* GetCSS() override;
@@ -79,6 +84,7 @@ class HeadlessDevToolsClientImpl : public HeadlessDevToolsClient,
inspector::Domain* GetInspector() override;
io::Domain* GetIO() override;
layer_tree::Domain* GetLayerTree() override;
+ log::Domain* GetLog() override;
memory::Domain* GetMemory() override;
network::Domain* GetNetwork() override;
page::Domain* GetPage() override;
@@ -143,17 +149,26 @@ class HeadlessDevToolsClientImpl : public HeadlessDevToolsClient,
void SendMessageWithoutParams(const char* method, CallbackType callback);
bool DispatchMessageReply(const base::DictionaryValue& message_dict);
- bool DispatchEvent(const base::DictionaryValue& message_dict);
+
+ using EventHandler = base::Callback<void(const base::Value&)>;
+ using EventHandlerMap = std::unordered_map<std::string, EventHandler>;
+
+ bool DispatchEvent(std::unique_ptr<base::Value> owning_message,
+ const base::DictionaryValue& message_dict);
+ void DispatchEventTask(std::unique_ptr<base::Value> owning_message,
+ const EventHandler* event_handler,
+ const base::DictionaryValue* result_dict);
content::DevToolsAgentHost* agent_host_; // Not owned.
int next_message_id_;
std::unordered_map<int, Callback> pending_messages_;
- std::unordered_map<std::string, base::Callback<void(const base::Value&)>>
- event_handlers_;
+
+ EventHandlerMap event_handlers_;
accessibility::ExperimentalDomain accessibility_domain_;
animation::ExperimentalDomain animation_domain_;
application_cache::ExperimentalDomain application_cache_domain_;
+ browser::ExperimentalDomain browser_domain_;
cache_storage::ExperimentalDomain cache_storage_domain_;
console::ExperimentalDomain console_domain_;
css::ExperimentalDomain css_domain_;
@@ -170,6 +185,7 @@ class HeadlessDevToolsClientImpl : public HeadlessDevToolsClient,
inspector::ExperimentalDomain inspector_domain_;
io::ExperimentalDomain io_domain_;
layer_tree::ExperimentalDomain layer_tree_domain_;
+ log::ExperimentalDomain log_domain_;
memory::ExperimentalDomain memory_domain_;
network::ExperimentalDomain network_domain_;
page::ExperimentalDomain page_domain_;
@@ -180,6 +196,8 @@ class HeadlessDevToolsClientImpl : public HeadlessDevToolsClient,
service_worker::ExperimentalDomain service_worker_domain_;
tracing::ExperimentalDomain tracing_domain_;
worker::ExperimentalDomain worker_domain_;
+ scoped_refptr<base::SingleThreadTaskRunner> browser_main_thread_;
+ base::WeakPtrFactory<HeadlessDevToolsClientImpl> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(HeadlessDevToolsClientImpl);
};
diff --git a/chromium/headless/lib/browser/headless_devtools_manager_delegate.cc b/chromium/headless/lib/browser/headless_devtools_manager_delegate.cc
new file mode 100644
index 00000000000..a53e8c773df
--- /dev/null
+++ b/chromium/headless/lib/browser/headless_devtools_manager_delegate.cc
@@ -0,0 +1,167 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/lib/browser/headless_devtools_manager_delegate.h"
+
+#include <string>
+#include <utility>
+
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/devtools_agent_host.h"
+#include "content/public/browser/devtools_frontend_host.h"
+#include "content/public/browser/web_contents.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_web_contents_impl.h"
+#include "headless/public/domains/browser.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace headless {
+
+HeadlessDevToolsManagerDelegate::HeadlessDevToolsManagerDelegate(
+ base::WeakPtr<HeadlessBrowserImpl> browser)
+ : browser_(std::move(browser)), default_browser_context_(nullptr) {
+ command_map_["Browser.createTarget"] =
+ &HeadlessDevToolsManagerDelegate::CreateTarget;
+ command_map_["Browser.closeTarget"] =
+ &HeadlessDevToolsManagerDelegate::CloseTarget;
+ command_map_["Browser.createBrowserContext"] =
+ &HeadlessDevToolsManagerDelegate::CreateBrowserContext;
+ command_map_["Browser.disposeBrowserContext"] =
+ &HeadlessDevToolsManagerDelegate::DisposeBrowserContext;
+}
+
+HeadlessDevToolsManagerDelegate::~HeadlessDevToolsManagerDelegate() {}
+
+base::DictionaryValue* HeadlessDevToolsManagerDelegate::HandleCommand(
+ content::DevToolsAgentHost* agent_host,
+ base::DictionaryValue* command) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ if (!browser_)
+ return nullptr;
+
+ int id;
+ std::string method;
+ const base::DictionaryValue* params = nullptr;
+ if (!command->GetInteger("id", &id) ||
+ !command->GetString("method", &method) ||
+ !command->GetDictionary("params", &params)) {
+ return nullptr;
+ }
+ auto find_it = command_map_.find(method);
+ if (find_it == command_map_.end())
+ return nullptr;
+ CommandMemberFnPtr command_fn_ptr = find_it->second;
+ std::unique_ptr<base::Value> cmd_result(((this)->*command_fn_ptr)(params));
+ if (!cmd_result)
+ return nullptr;
+
+ std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue());
+ result->SetInteger("id", id);
+ result->Set("result", std::move(cmd_result));
+ return result.release();
+}
+
+std::string HeadlessDevToolsManagerDelegate::GetDiscoveryPageHTML() {
+ return ResourceBundle::GetSharedInstance().GetRawDataResource(
+ IDR_HEADLESS_LIB_DEVTOOLS_DISCOVERY_PAGE).as_string();
+}
+
+std::string HeadlessDevToolsManagerDelegate::GetFrontendResource(
+ const std::string& path) {
+ return content::DevToolsFrontendHost::GetFrontendResource(path).as_string();
+}
+
+std::unique_ptr<base::Value> HeadlessDevToolsManagerDelegate::CreateTarget(
+ const base::DictionaryValue* params) {
+ std::string url;
+ std::string browser_context_id;
+ int width = browser_->options()->window_size.width();
+ int height = browser_->options()->window_size.height();
+ params->GetString("url", &url);
+ params->GetString("browserContextId", &browser_context_id);
+ params->GetInteger("width", &width);
+ params->GetInteger("height", &height);
+
+ // TODO(alexclarke): Should we fail when user passes incorrect id?
+ HeadlessBrowserContext* context =
+ browser_->GetBrowserContextForId(browser_context_id);
+ if (!context) {
+ if (!default_browser_context_) {
+ default_browser_context_ =
+ browser_->CreateBrowserContextBuilder().Build();
+ }
+ context = default_browser_context_;
+ }
+
+ HeadlessWebContentsImpl* web_contents_impl =
+ HeadlessWebContentsImpl::From(context->CreateWebContentsBuilder()
+ .SetInitialURL(GURL(url))
+ .SetWindowSize(gfx::Size(width, height))
+ .Build());
+
+ return browser::CreateTargetResult::Builder()
+ .SetTargetId(web_contents_impl->GetDevToolsAgentHostId())
+ .Build()
+ ->Serialize();
+}
+
+std::unique_ptr<base::Value> HeadlessDevToolsManagerDelegate::CloseTarget(
+ const base::DictionaryValue* params) {
+ std::string target_id;
+ if (!params->GetString("targetId", &target_id)) {
+ return nullptr;
+ }
+ HeadlessWebContents* web_contents =
+ browser_->GetWebContentsForDevToolsAgentHostId(target_id);
+ bool success = false;
+ if (web_contents) {
+ web_contents->Close();
+ success = true;
+ }
+ return browser::CloseTargetResult::Builder()
+ .SetSuccess(success)
+ .Build()
+ ->Serialize();
+}
+
+std::unique_ptr<base::Value>
+HeadlessDevToolsManagerDelegate::CreateBrowserContext(
+ const base::DictionaryValue* params) {
+ HeadlessBrowserContext* browser_context =
+ browser_->CreateBrowserContextBuilder().Build();
+
+ std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue());
+ return browser::CreateBrowserContextResult::Builder()
+ .SetBrowserContextId(browser_context->Id())
+ .Build()
+ ->Serialize();
+}
+
+std::unique_ptr<base::Value>
+HeadlessDevToolsManagerDelegate::DisposeBrowserContext(
+ const base::DictionaryValue* params) {
+ std::string browser_context_id;
+ if (!params->GetString("browserContextId", &browser_context_id)) {
+ return nullptr;
+ }
+ HeadlessBrowserContext* context =
+ browser_->GetBrowserContextForId(browser_context_id);
+
+ bool success = false;
+ if (context && context != default_browser_context_ &&
+ context->GetAllWebContents().empty()) {
+ success = true;
+ context->Close();
+ }
+
+ return browser::DisposeBrowserContextResult::Builder()
+ .SetSuccess(success)
+ .Build()
+ ->Serialize();
+}
+
+} // namespace headless
diff --git a/chromium/headless/lib/browser/headless_devtools_manager_delegate.h b/chromium/headless/lib/browser/headless_devtools_manager_delegate.h
new file mode 100644
index 00000000000..4d43fc76b79
--- /dev/null
+++ b/chromium/headless/lib/browser/headless_devtools_manager_delegate.h
@@ -0,0 +1,56 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_LIB_BROWSER_HEADLESS_DEVTOOLS_MANAGER_DELEGATE_H_
+#define HEADLESS_LIB_BROWSER_HEADLESS_DEVTOOLS_MANAGER_DELEGATE_H_
+
+#include "content/public/browser/devtools_manager_delegate.h"
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+
+namespace headless {
+class HeadlessBrowserImpl;
+class HeadlessBrowserContext;
+class HeadlessWebContentsImpl;
+
+class HeadlessDevToolsManagerDelegate
+ : public content::DevToolsManagerDelegate {
+ public:
+ explicit HeadlessDevToolsManagerDelegate(
+ base::WeakPtr<HeadlessBrowserImpl> browser);
+ ~HeadlessDevToolsManagerDelegate() override;
+
+ // DevToolsManagerDelegate implementation:
+ base::DictionaryValue* HandleCommand(content::DevToolsAgentHost* agent_host,
+ base::DictionaryValue* command) override;
+ std::string GetDiscoveryPageHTML() override;
+ std::string GetFrontendResource(const std::string& path) override;
+
+ private:
+ std::unique_ptr<base::Value> CreateTarget(
+ const base::DictionaryValue* params);
+ std::unique_ptr<base::Value> CloseTarget(const base::DictionaryValue* params);
+ std::unique_ptr<base::Value> CreateBrowserContext(
+ const base::DictionaryValue* params);
+ std::unique_ptr<base::Value> DisposeBrowserContext(
+ const base::DictionaryValue* params);
+
+ base::WeakPtr<HeadlessBrowserImpl> browser_;
+
+ using CommandMemberFnPtr = std::unique_ptr<base::Value> (
+ HeadlessDevToolsManagerDelegate::*)(const base::DictionaryValue* params);
+
+ std::map<std::string, CommandMemberFnPtr> command_map_;
+
+ HeadlessBrowserContext* default_browser_context_;
+};
+
+} // namespace headless
+
+#endif // HEADLESS_LIB_BROWSER_HEADLESS_DEVTOOLS_MANAGER_DELEGATE_H_
diff --git a/chromium/headless/lib/browser/headless_screen.cc b/chromium/headless/lib/browser/headless_screen.cc
index e7be71a515b..c8be18bfdb1 100644
--- a/chromium/headless/lib/browser/headless_screen.cc
+++ b/chromium/headless/lib/browser/headless_screen.cc
@@ -30,8 +30,7 @@ bool IsRotationPortrait(display::Display::Rotation rotation) {
// static
HeadlessScreen* HeadlessScreen::Create(const gfx::Size& size) {
- const gfx::Size kDefaultSize(800, 600);
- return new HeadlessScreen(gfx::Rect(size.IsEmpty() ? kDefaultSize : size));
+ return new HeadlessScreen(gfx::Rect(size));
}
HeadlessScreen::~HeadlessScreen() {}
diff --git a/chromium/headless/lib/browser/headless_screen.h b/chromium/headless/lib/browser/headless_screen.h
index f0c363c54f6..962fc9c61b8 100644
--- a/chromium/headless/lib/browser/headless_screen.h
+++ b/chromium/headless/lib/browser/headless_screen.h
@@ -27,8 +27,7 @@ namespace headless {
class HeadlessScreen : public display::Screen, public aura::WindowObserver {
public:
- // Creates a display::Screen of the specified size. If no size is specified,
- // then creates a 800x600 screen. |size| is in physical pixels.
+ // Creates a display::Screen of the specified size (physical pixels).
static HeadlessScreen* Create(const gfx::Size& size);
~HeadlessScreen() override;
diff --git a/chromium/headless/lib/browser/headless_url_request_context_getter.cc b/chromium/headless/lib/browser/headless_url_request_context_getter.cc
index d253e5c267f..04a04d5c25a 100644
--- a/chromium/headless/lib/browser/headless_url_request_context_getter.cc
+++ b/chromium/headless/lib/browser/headless_url_request_context_getter.cc
@@ -5,10 +5,13 @@
#include "headless/lib/browser/headless_url_request_context_getter.h"
#include <memory>
+#include <utility>
+#include <vector>
#include "base/memory/ptr_util.h"
#include "base/single_thread_task_runner.h"
#include "content/public/browser/browser_thread.h"
+#include "headless/lib/browser/headless_browser_context_options.h"
#include "net/dns/mapped_host_resolver.h"
#include "net/proxy/proxy_service.h"
#include "net/url_request/url_request_context.h"
@@ -22,23 +25,18 @@ HeadlessURLRequestContextGetter::HeadlessURLRequestContextGetter(
content::ProtocolHandlerMap* protocol_handlers,
ProtocolHandlerMap context_protocol_handlers,
content::URLRequestInterceptorScopedVector request_interceptors,
- HeadlessBrowser::Options* options)
+ HeadlessBrowserContextOptions* options)
: io_task_runner_(std::move(io_task_runner)),
file_task_runner_(std::move(file_task_runner)),
- user_agent_(options->user_agent),
- host_resolver_rules_(options->host_resolver_rules),
- proxy_server_(options->proxy_server),
+ user_agent_(options->user_agent()),
+ host_resolver_rules_(options->host_resolver_rules()),
+ proxy_server_(options->proxy_server()),
request_interceptors_(std::move(request_interceptors)) {
// Must first be created on the UI thread.
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::swap(protocol_handlers_, *protocol_handlers);
- for (auto& pair : options->protocol_handlers) {
- protocol_handlers_[pair.first] =
- linked_ptr<net::URLRequestJobFactory::ProtocolHandler>(
- pair.second.release());
- }
- options->protocol_handlers.clear();
+
for (auto& pair : context_protocol_handlers) {
protocol_handlers_[pair.first] =
linked_ptr<net::URLRequestJobFactory::ProtocolHandler>(
@@ -67,9 +65,8 @@ HeadlessURLRequestContextGetter::GetURLRequestContext() {
// TODO(skyostil): Make these configurable.
builder.set_data_enabled(true);
builder.set_file_enabled(true);
- builder.SetFileTaskRunner(
- content::BrowserThread::GetMessageLoopProxyForThread(
- content::BrowserThread::FILE));
+ builder.SetFileTaskRunner(content::BrowserThread::GetTaskRunnerForThread(
+ content::BrowserThread::FILE));
if (!proxy_server_.IsEmpty()) {
builder.set_proxy_service(
net::ProxyService::CreateFixed(proxy_server_.ToString()));
@@ -108,7 +105,7 @@ HeadlessURLRequestContextGetter::GetURLRequestContext() {
scoped_refptr<base::SingleThreadTaskRunner>
HeadlessURLRequestContextGetter::GetNetworkTaskRunner() const {
- return content::BrowserThread::GetMessageLoopProxyForThread(
+ return content::BrowserThread::GetTaskRunnerForThread(
content::BrowserThread::IO);
}
diff --git a/chromium/headless/lib/browser/headless_url_request_context_getter.h b/chromium/headless/lib/browser/headless_url_request_context_getter.h
index 5556ef19126..166f6a9cbe6 100644
--- a/chromium/headless/lib/browser/headless_url_request_context_getter.h
+++ b/chromium/headless/lib/browser/headless_url_request_context_getter.h
@@ -6,6 +6,7 @@
#define HEADLESS_LIB_BROWSER_HEADLESS_URL_REQUEST_CONTEXT_GETTER_H_
#include <memory>
+#include <string>
#include "base/compiler_specific.h"
#include "base/files/file_path.h"
@@ -33,6 +34,7 @@ class URLRequestContextStorage;
}
namespace headless {
+class HeadlessBrowserContextOptions;
class HeadlessURLRequestContextGetter : public net::URLRequestContextGetter {
public:
@@ -42,7 +44,7 @@ class HeadlessURLRequestContextGetter : public net::URLRequestContextGetter {
content::ProtocolHandlerMap* protocol_handlers,
ProtocolHandlerMap context_protocol_handlers,
content::URLRequestInterceptorScopedVector request_interceptors,
- HeadlessBrowser::Options* options);
+ HeadlessBrowserContextOptions* options);
// net::URLRequestContextGetter implementation:
net::URLRequestContext* GetURLRequestContext() override;
diff --git a/chromium/headless/lib/browser/headless_web_contents_impl.cc b/chromium/headless/lib/browser/headless_web_contents_impl.cc
index b07cb2c7ede..20865a3da63 100644
--- a/chromium/headless/lib/browser/headless_web_contents_impl.cc
+++ b/chromium/headless/lib/browser/headless_web_contents_impl.cc
@@ -4,10 +4,16 @@
#include "headless/lib/browser/headless_web_contents_impl.h"
+#include <string>
+#include <utility>
+
#include "base/bind.h"
+#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
+#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
+#include "content/public/browser/browser_thread.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
@@ -16,17 +22,25 @@
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
-#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/renderer/render_frame.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_client_impl.h"
+#include "services/shell/public/cpp/interface_registry.h"
#include "ui/aura/window.h"
namespace headless {
+// static
+HeadlessWebContentsImpl* HeadlessWebContentsImpl::From(
+ HeadlessWebContents* web_contents) {
+ // This downcast is safe because there is only one implementation of
+ // HeadlessWebContents.
+ return static_cast<HeadlessWebContentsImpl*>(web_contents);
+}
+
class WebContentsObserverAdapter : public content::WebContentsObserver {
public:
WebContentsObserverAdapter(content::WebContents* web_contents,
@@ -48,36 +62,43 @@ class WebContentsObserverAdapter : public content::WebContentsObserver {
class HeadlessWebContentsImpl::Delegate : public content::WebContentsDelegate {
public:
- explicit Delegate(HeadlessBrowserImpl* browser) : browser_(browser) {}
+ explicit Delegate(HeadlessBrowserContextImpl* browser_context)
+ : browser_context_(browser_context) {}
void WebContentsCreated(content::WebContents* source_contents,
+ int opener_render_process_id,
int opener_render_frame_id,
const std::string& frame_name,
const GURL& target_url,
content::WebContents* new_contents) override {
- browser_->RegisterWebContents(
- HeadlessWebContentsImpl::CreateFromWebContents(new_contents, browser_));
+ std::unique_ptr<HeadlessWebContentsImpl> web_contents =
+ HeadlessWebContentsImpl::CreateFromWebContents(new_contents,
+ browser_context_);
+
+ DCHECK(new_contents->GetBrowserContext() == browser_context_);
+
+ browser_context_->RegisterWebContents(std::move(web_contents));
}
private:
- HeadlessBrowserImpl* browser_; // Not owned.
+ HeadlessBrowserContextImpl* browser_context_; // Not owned.
DISALLOW_COPY_AND_ASSIGN(Delegate);
};
// static
std::unique_ptr<HeadlessWebContentsImpl> HeadlessWebContentsImpl::Create(
HeadlessWebContents::Builder* builder,
- aura::Window* parent_window,
- HeadlessBrowserImpl* browser) {
- content::BrowserContext* context =
- HeadlessBrowserContextImpl::From(builder->browser_context_);
- content::WebContents::CreateParams create_params(context, nullptr);
+ aura::Window* parent_window) {
+ content::WebContents::CreateParams create_params(builder->browser_context_,
+ nullptr);
create_params.initial_size = builder->window_size_;
std::unique_ptr<HeadlessWebContentsImpl> headless_web_contents =
base::WrapUnique(new HeadlessWebContentsImpl(
- content::WebContents::Create(create_params), browser));
+ content::WebContents::Create(create_params),
+ builder->browser_context_));
+ headless_web_contents->mojo_services_ = std::move(builder->mojo_services_);
headless_web_contents->InitializeScreen(parent_window, builder->window_size_);
if (!headless_web_contents->OpenURL(builder->initial_url_))
return nullptr;
@@ -88,9 +109,10 @@ std::unique_ptr<HeadlessWebContentsImpl> HeadlessWebContentsImpl::Create(
std::unique_ptr<HeadlessWebContentsImpl>
HeadlessWebContentsImpl::CreateFromWebContents(
content::WebContents* web_contents,
- HeadlessBrowserImpl* browser) {
+ HeadlessBrowserContextImpl* browser_context) {
std::unique_ptr<HeadlessWebContentsImpl> headless_web_contents =
- base::WrapUnique(new HeadlessWebContentsImpl(web_contents, browser));
+ base::WrapUnique(
+ new HeadlessWebContentsImpl(web_contents, browser_context));
return headless_web_contents;
}
@@ -111,10 +133,13 @@ void HeadlessWebContentsImpl::InitializeScreen(aura::Window* parent_window,
HeadlessWebContentsImpl::HeadlessWebContentsImpl(
content::WebContents* web_contents,
- HeadlessBrowserImpl* browser)
- : web_contents_delegate_(new HeadlessWebContentsImpl::Delegate(browser)),
+ HeadlessBrowserContextImpl* browser_context)
+ : content::WebContentsObserver(web_contents),
+ web_contents_delegate_(
+ new HeadlessWebContentsImpl::Delegate(browser_context)),
web_contents_(web_contents),
- browser_(browser) {
+ agent_host_(content::DevToolsAgentHost::GetOrCreateFor(web_contents)),
+ browser_context_(browser_context) {
web_contents_->SetDelegate(web_contents_delegate_.get());
}
@@ -122,6 +147,23 @@ HeadlessWebContentsImpl::~HeadlessWebContentsImpl() {
web_contents_->Close();
}
+void HeadlessWebContentsImpl::RenderFrameCreated(
+ content::RenderFrameHost* render_frame_host) {
+ if (!mojo_services_.empty()) {
+ render_frame_host->GetRenderViewHost()->AllowBindings(
+ content::BINDINGS_POLICY_HEADLESS);
+ }
+
+ shell::InterfaceRegistry* interface_registry =
+ render_frame_host->GetInterfaceRegistry();
+
+ for (const MojoService& service : mojo_services_) {
+ interface_registry->AddInterface(service.service_name,
+ service.service_factory,
+ browser()->BrowserMainThread());
+ }
+}
+
bool HeadlessWebContentsImpl::OpenURL(const GURL& url) {
if (!url.is_valid())
return false;
@@ -134,13 +176,18 @@ bool HeadlessWebContentsImpl::OpenURL(const GURL& url) {
}
void HeadlessWebContentsImpl::Close() {
- browser_->DestroyWebContents(this);
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ browser_context()->DestroyWebContents(this);
+}
+
+std::string HeadlessWebContentsImpl::GetDevToolsAgentHostId() {
+ return agent_host_->GetId();
}
void HeadlessWebContentsImpl::AddObserver(Observer* observer) {
DCHECK(observer_map_.find(observer) == observer_map_.end());
- observer_map_[observer] = base::WrapUnique(
- new WebContentsObserverAdapter(web_contents_.get(), observer));
+ observer_map_[observer] = base::MakeUnique<WebContentsObserverAdapter>(
+ web_contents_.get(), observer);
}
void HeadlessWebContentsImpl::RemoveObserver(Observer* observer) {
@@ -154,8 +201,6 @@ HeadlessDevToolsTarget* HeadlessWebContentsImpl::GetDevToolsTarget() {
}
void HeadlessWebContentsImpl::AttachClient(HeadlessDevToolsClient* client) {
- if (!agent_host_)
- agent_host_ = content::DevToolsAgentHost::GetOrCreateFor(web_contents());
HeadlessDevToolsClientImpl::From(client)->AttachToHost(agent_host_.get());
}
@@ -168,10 +213,18 @@ content::WebContents* HeadlessWebContentsImpl::web_contents() const {
return web_contents_.get();
}
-HeadlessWebContents::Builder::Builder(HeadlessBrowserImpl* browser)
- : browser_(browser),
- browser_context_(
- browser->browser_main_parts()->default_browser_context()) {}
+HeadlessBrowserImpl* HeadlessWebContentsImpl::browser() const {
+ return browser_context_->browser();
+}
+
+HeadlessBrowserContextImpl* HeadlessWebContentsImpl::browser_context() const {
+ return browser_context_;
+}
+
+HeadlessWebContents::Builder::Builder(
+ HeadlessBrowserContextImpl* browser_context)
+ : browser_context_(browser_context),
+ window_size_(browser_context->options()->window_size()) {}
HeadlessWebContents::Builder::~Builder() = default;
@@ -189,14 +242,25 @@ HeadlessWebContents::Builder& HeadlessWebContents::Builder::SetWindowSize(
return *this;
}
-HeadlessWebContents::Builder& HeadlessWebContents::Builder::SetBrowserContext(
- HeadlessBrowserContext* browser_context) {
- browser_context_ = browser_context;
+HeadlessWebContents::Builder& HeadlessWebContents::Builder::AddMojoService(
+ const std::string& service_name,
+ const base::Callback<void(mojo::ScopedMessagePipeHandle)>&
+ service_factory) {
+ mojo_services_.emplace_back(service_name, service_factory);
return *this;
}
HeadlessWebContents* HeadlessWebContents::Builder::Build() {
- return browser_->CreateWebContents(this);
+ return browser_context_->CreateWebContents(this);
}
+HeadlessWebContents::Builder::MojoService::MojoService() {}
+
+HeadlessWebContents::Builder::MojoService::MojoService(
+ const std::string& service_name,
+ const base::Callback<void(mojo::ScopedMessagePipeHandle)>& service_factory)
+ : service_name(service_name), service_factory(service_factory) {}
+
+HeadlessWebContents::Builder::MojoService::~MojoService() {}
+
} // namespace headless
diff --git a/chromium/headless/lib/browser/headless_web_contents_impl.h b/chromium/headless/lib/browser/headless_web_contents_impl.h
index e0eec04afa2..43293847b35 100644
--- a/chromium/headless/lib/browser/headless_web_contents_impl.h
+++ b/chromium/headless/lib/browser/headless_web_contents_impl.h
@@ -5,12 +5,15 @@
#ifndef HEADLESS_LIB_BROWSER_HEADLESS_WEB_CONTENTS_IMPL_H_
#define HEADLESS_LIB_BROWSER_HEADLESS_WEB_CONTENTS_IMPL_H_
-#include "headless/public/headless_devtools_target.h"
-#include "headless/public/headless_web_contents.h"
-
+#include <list>
#include <memory>
+#include <string>
#include <unordered_map>
+#include "content/public/browser/web_contents_observer.h"
+#include "headless/public/headless_devtools_target.h"
+#include "headless/public/headless_web_contents.h"
+
namespace aura {
class Window;
}
@@ -31,19 +34,21 @@ class HeadlessBrowserImpl;
class WebContentsObserverAdapter;
class HeadlessWebContentsImpl : public HeadlessWebContents,
- public HeadlessDevToolsTarget {
+ public HeadlessDevToolsTarget,
+ public content::WebContentsObserver {
public:
~HeadlessWebContentsImpl() override;
+ static HeadlessWebContentsImpl* From(HeadlessWebContents* web_contents);
+
static std::unique_ptr<HeadlessWebContentsImpl> Create(
HeadlessWebContents::Builder* builder,
- aura::Window* parent_window,
- HeadlessBrowserImpl* browser);
+ aura::Window* parent_window);
// Takes ownership of |web_contents|.
static std::unique_ptr<HeadlessWebContentsImpl> CreateFromWebContents(
content::WebContents* web_contents,
- HeadlessBrowserImpl* browser);
+ HeadlessBrowserContextImpl* browser_context);
// HeadlessWebContents implementation:
void AddObserver(Observer* observer) override;
@@ -54,25 +59,36 @@ class HeadlessWebContentsImpl : public HeadlessWebContents,
void AttachClient(HeadlessDevToolsClient* client) override;
void DetachClient(HeadlessDevToolsClient* client) override;
+ // content::WebContentsObserver implementation:
+ void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override;
+
content::WebContents* web_contents() const;
bool OpenURL(const GURL& url);
void Close() override;
+ std::string GetDevToolsAgentHostId();
+
+ HeadlessBrowserImpl* browser() const;
+ HeadlessBrowserContextImpl* browser_context() const;
+
private:
// Takes ownership of |web_contents|.
HeadlessWebContentsImpl(content::WebContents* web_contents,
- HeadlessBrowserImpl* browser);
+ HeadlessBrowserContextImpl* browser_context);
void InitializeScreen(aura::Window* parent_window,
const gfx::Size& initial_size);
+ using MojoService = HeadlessWebContents::Builder::MojoService;
+
class Delegate;
std::unique_ptr<Delegate> web_contents_delegate_;
std::unique_ptr<content::WebContents> web_contents_;
scoped_refptr<content::DevToolsAgentHost> agent_host_;
+ std::list<MojoService> mojo_services_;
- HeadlessBrowserImpl* browser_; // Not owned.
+ HeadlessBrowserContextImpl* browser_context_; // Not owned.
using ObserverMap =
std::unordered_map<HeadlessWebContents::Observer*,
diff --git a/chromium/headless/lib/browser/type_conversions_h.template b/chromium/headless/lib/browser/type_conversions_h.template
index 286b0e5ea65..1c918ac9ea3 100644
--- a/chromium/headless/lib/browser/type_conversions_h.template
+++ b/chromium/headless/lib/browser/type_conversions_h.template
@@ -20,7 +20,7 @@ namespace internal {
template <>
struct FromValue<{{namespace}}::{{type.id}}> {
static {{namespace}}::{{type.id}} Parse(const base::Value& value, ErrorReporter* errors) {
- {% set default = namespace + '::' + type.id + '::' + type.enum[0] | dash_to_camelcase | camelcase_to_hacker_style | upper | mangle_enum %}
+ {% set default = namespace + '::' + type.id + '::' + type.enum[0] | sanitize_literal | dash_to_camelcase | camelcase_to_hacker_style | upper %}
std::string string_value;
if (!value.GetAsString(&string_value)) {
errors->AddError("string enum value expected");
@@ -29,7 +29,7 @@ struct FromValue<{{namespace}}::{{type.id}}> {
}
{% for literal in type.enum %}
if (string_value == "{{literal}}")
- return {{namespace}}::{{type.id}}::{{literal | dash_to_camelcase | camelcase_to_hacker_style | upper | mangle_enum}};
+ return {{namespace}}::{{type.id}}::{{literal | sanitize_literal | dash_to_camelcase | camelcase_to_hacker_style | upper }};
{% endfor %}
errors->AddError("invalid enum value");
return {{default}};
@@ -40,7 +40,7 @@ template <typename T>
std::unique_ptr<base::Value> ToValueImpl(const {{namespace}}::{{type.id}}& value, T*) {
switch (value) {
{% for literal in type.enum %}
- case {{namespace}}::{{type.id}}::{{literal | dash_to_camelcase | camelcase_to_hacker_style | upper | mangle_enum}}:
+ case {{namespace}}::{{type.id}}::{{literal | sanitize_literal | dash_to_camelcase | camelcase_to_hacker_style | upper }}:
return base::WrapUnique(new base::StringValue("{{literal}}"));
{% endfor %}
};
diff --git a/chromium/headless/lib/browser/types_h.template b/chromium/headless/lib/browser/types_h.template
index 25b341d89dd..84b09cbeb24 100644
--- a/chromium/headless/lib/browser/types_h.template
+++ b/chromium/headless/lib/browser/types_h.template
@@ -39,7 +39,7 @@ namespace {{domain.domain | camelcase_to_hacker_style}} {
{% if "enum" in type %}
enum class {{type.id}} {
{% for literal in type.enum %}
- {{ literal | dash_to_camelcase | camelcase_to_hacker_style | upper | mangle_enum}}{{',' if not loop.last}}
+ {{ literal | sanitize_literal | dash_to_camelcase | camelcase_to_hacker_style | upper }}{{',' if not loop.last}}
{% endfor %}
};
@@ -70,7 +70,7 @@ class HEADLESS_EXPORT {{type.id}} {
{% endif %}
{% if property.optional %}
bool Has{{property.name | to_title_case}}() const { return !!{{property.name | camelcase_to_hacker_style}}_; }
- {{resolve_type(property).raw_return_type}} Get{{property.name | to_title_case}}() const { return {{resolve_type(property).to_raw_return_type % ("%s_.value()" % property.name | camelcase_to_hacker_style)}}; }
+ {{resolve_type(property).raw_return_type}} Get{{property.name | to_title_case}}() const { DCHECK(Has{{property.name | to_title_case}}()); return {{resolve_type(property).to_raw_return_type % ("%s_.value()" % property.name | camelcase_to_hacker_style)}}; }
void Set{{property.name | to_title_case}}({{resolve_type(property).pass_type}} value) { {{property.name | camelcase_to_hacker_style}}_ = {{resolve_type(property).to_pass_type % 'value'}}; }
{% else %}
{{resolve_type(property).raw_return_type}} Get{{property.name | to_title_case}}() const { return {{resolve_type(property).to_raw_return_type % ("%s_" % property.name | camelcase_to_hacker_style)}}; }
diff --git a/chromium/headless/lib/embedder_mojo_browsertest.cc b/chromium/headless/lib/embedder_mojo_browsertest.cc
new file mode 100644
index 00000000000..a8338e04801
--- /dev/null
+++ b/chromium/headless/lib/embedder_mojo_browsertest.cc
@@ -0,0 +1,247 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include "base/optional.h"
+#include "base/path_service.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_restrictions.h"
+#include "content/public/test/browser_test.h"
+#include "headless/grit/headless_browsertest_resources.h"
+#include "headless/lib/embedder_test.mojom.h"
+#include "headless/public/domains/network.h"
+#include "headless/public/domains/page.h"
+#include "headless/public/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/test/headless_browser_test.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "mojo/public/cpp/bindings/interface_ptr_set.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "url/gurl.h"
+
+namespace headless {
+
+#define DEVTOOLS_CLIENT_TEST_F(TEST_FIXTURE_NAME) \
+ IN_PROC_BROWSER_TEST_F(TEST_FIXTURE_NAME, RunAsyncTest) { RunTest(); } \
+ class AsyncHeadlessBrowserTestNeedsSemicolon##TEST_FIXTURE_NAME {}
+
+// A test fixture which attaches a devtools client and registers a mojo
+// interface before starting the test.
+class EmbedderMojoTest : public HeadlessBrowserTest,
+ public HeadlessWebContents::Observer,
+ public embedder_test::TestEmbedderService {
+ public:
+ enum HttpPolicy { DEFAULT, ENABLE_HTTP };
+
+ EmbedderMojoTest() : EmbedderMojoTest(HttpPolicy::DEFAULT) {}
+
+ explicit EmbedderMojoTest(HttpPolicy http_policy)
+ : browser_context_(nullptr),
+ web_contents_(nullptr),
+ devtools_client_(HeadlessDevToolsClient::Create()),
+ http_policy_(http_policy) {}
+
+ ~EmbedderMojoTest() override {}
+
+ void SetUpOnMainThread() override {
+ base::ThreadRestrictions::SetIOAllowed(true);
+ base::FilePath pak_path;
+ ASSERT_TRUE(PathService::Get(base::DIR_MODULE, &pak_path));
+ pak_path = pak_path.AppendASCII("headless_browser_tests.pak");
+ ResourceBundle::GetSharedInstance().AddDataPackFromPath(
+ pak_path, ui::SCALE_FACTOR_NONE);
+ }
+
+ // HeadlessWebContentsObserver implementation:
+ void DevToolsTargetReady() override {
+ EXPECT_TRUE(web_contents_->GetDevToolsTarget());
+ web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get());
+
+ RunMojoTest();
+ }
+
+ virtual void RunMojoTest() = 0;
+
+ virtual GURL GetInitialUrl() const { return GURL("about:blank"); }
+
+ void OnEvalResult(std::unique_ptr<runtime::EvaluateResult> result) {
+ EXPECT_FALSE(result->HasExceptionDetails())
+ << "JS exception: " << result->GetExceptionDetails()->GetText();
+ if (result->HasExceptionDetails()) {
+ FinishAsynchronousTest();
+ }
+ }
+
+ protected:
+ void RunTest() {
+ // Using a pak file is idiomatic chromium style, but most embedders probably
+ // wouln't load the javascript bindings file this way.
+ std::string embedder_test_mojom_js =
+ ResourceBundle::GetSharedInstance()
+ .GetRawDataResource(IDR_HEADLESS_EMBEDDER_TEST_MOJOM_JS)
+ .as_string();
+
+ HeadlessBrowserContext::Builder builder =
+ browser()->CreateBrowserContextBuilder();
+ builder.AddJsMojoBindings("headless/lib/embedder_test.mojom",
+ embedder_test_mojom_js);
+ if (http_policy_ == HttpPolicy::ENABLE_HTTP) {
+ builder.EnableUnsafeNetworkAccessWithMojoBindings(true);
+ }
+ if (host_resolver_rules_) {
+ builder.SetHostResolverRules(*host_resolver_rules_);
+ }
+
+ browser_context_ = builder.Build();
+
+ web_contents_ =
+ browser_context_->CreateWebContentsBuilder()
+ .SetInitialURL(GURL(GetInitialUrl()))
+ .AddMojoService(base::Bind(&EmbedderMojoTest::CreateTestMojoService,
+ base::Unretained(this)))
+ .Build();
+
+ web_contents_->AddObserver(this);
+ RunAsynchronousTest();
+
+ web_contents_->GetDevToolsTarget()->DetachClient(devtools_client_.get());
+ web_contents_->RemoveObserver(this);
+ web_contents_->Close();
+ web_contents_ = nullptr;
+
+ browser_context_->Close();
+ browser_context_ = nullptr;
+ }
+
+ void CreateTestMojoService(
+ mojo::InterfaceRequest<embedder_test::TestEmbedderService> request) {
+ test_embedder_mojo_bindings_.AddBinding(this, std::move(request));
+ }
+
+ HeadlessBrowserContext* browser_context_;
+ HeadlessWebContents* web_contents_;
+ std::unique_ptr<HeadlessDevToolsClient> devtools_client_;
+
+ mojo::BindingSet<embedder_test::TestEmbedderService>
+ test_embedder_mojo_bindings_;
+
+ HttpPolicy http_policy_;
+ base::Optional<std::string> host_resolver_rules_;
+};
+
+class MojoBindingsTest : public EmbedderMojoTest {
+ public:
+ void RunMojoTest() override {
+ devtools_client_->GetRuntime()->Evaluate(
+ "// Note define() defines a module in the mojo module dependency \n"
+ "// system. While we don't expose our module, the callback below only\n"
+ "// fires after the requested modules have been loaded. \n"
+ "define([ \n"
+ " 'headless/lib/embedder_test.mojom', \n"
+ " 'mojo/public/js/core', \n"
+ " 'mojo/public/js/router', \n"
+ " 'content/public/renderer/frame_interfaces', \n"
+ " ], function(embedderMojom, mojoCore, routerModule, \n"
+ " frameInterfaces) { \n"
+ " var testEmbedderService = \n"
+ " new embedderMojom.TestEmbedderService.proxyClass( \n"
+ " new routerModule.Router( \n"
+ " frameInterfaces.getInterface( \n"
+ " embedderMojom.TestEmbedderService.name))); \n"
+ " \n"
+ " // Send a message to the embedder! \n"
+ " testEmbedderService.returnTestResult('hello world'); \n"
+ "});",
+ base::Bind(&EmbedderMojoTest::OnEvalResult, base::Unretained(this)));
+ }
+
+ // embedder_test::TestEmbedderService:
+ void ReturnTestResult(const std::string& result) override {
+ EXPECT_EQ("hello world", result);
+ FinishAsynchronousTest();
+ }
+};
+
+DEVTOOLS_CLIENT_TEST_F(MojoBindingsTest);
+
+class MojoBindingsReinstalledAfterNavigation : public EmbedderMojoTest {
+ public:
+ MojoBindingsReinstalledAfterNavigation()
+ : EmbedderMojoTest(HttpPolicy::ENABLE_HTTP), seen_page_one_(false) {
+ EXPECT_TRUE(embedded_test_server()->Start());
+ }
+
+ void SetUpOnMainThread() override {
+ // We want to make sure bindings work across browser initiated cross-origin
+ // navigation, which is why we're setting up this fake tld.
+ host_resolver_rules_ =
+ base::StringPrintf("MAP not-an-actual-domain.tld 127.0.0.1:%d",
+ embedded_test_server()->host_port_pair().port());
+
+ EmbedderMojoTest::SetUpOnMainThread();
+ }
+
+ void RunMojoTest() override {}
+
+ GURL GetInitialUrl() const override {
+ return embedded_test_server()->GetURL("/page_one.html");
+ }
+
+ // embedder_test::TestEmbedderService:
+ void ReturnTestResult(const std::string& result) override {
+ if (result == "page one") {
+ seen_page_one_ = true;
+ devtools_client_->GetPage()->Navigate(
+ "http://not-an-actual-domain.tld/page_two.html");
+ } else {
+ EXPECT_TRUE(seen_page_one_);
+ EXPECT_EQ("page two", result);
+ FinishAsynchronousTest();
+ }
+ }
+
+ private:
+ bool seen_page_one_;
+};
+
+DEVTOOLS_CLIENT_TEST_F(MojoBindingsReinstalledAfterNavigation);
+
+class HttpDisabledByDefaultWhenMojoBindingsUsed : public EmbedderMojoTest,
+ network::Observer,
+ page::Observer {
+ public:
+ HttpDisabledByDefaultWhenMojoBindingsUsed() {
+ EXPECT_TRUE(embedded_test_server()->Start());
+ }
+
+ void RunMojoTest() override {
+ devtools_client_->GetNetwork()->AddObserver(this);
+ devtools_client_->GetNetwork()->Enable();
+ }
+
+ GURL GetInitialUrl() const override {
+ return embedded_test_server()->GetURL("/page_one.html");
+ }
+
+ void ReturnTestResult(const std::string& result) override {
+ FinishAsynchronousTest();
+ FAIL() << "The HTTP page should not have been served and we should not have"
+ " recieved a mojo callback!";
+ }
+
+ void OnLoadingFailed(const network::LoadingFailedParams& params) override {
+ // The navigation should fail since HTTP requests are blackholed.
+ EXPECT_EQ(params.GetErrorText(), "net::ERR_FILE_NOT_FOUND");
+ FinishAsynchronousTest();
+ }
+};
+
+DEVTOOLS_CLIENT_TEST_F(HttpDisabledByDefaultWhenMojoBindingsUsed);
+
+} // namespace headless
diff --git a/chromium/headless/lib/embedder_test.mojom b/chromium/headless/lib/embedder_test.mojom
new file mode 100644
index 00000000000..553709a1986
--- /dev/null
+++ b/chromium/headless/lib/embedder_test.mojom
@@ -0,0 +1,11 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module embedder_test;
+
+// An example embedder Mojo service.
+interface TestEmbedderService {
+ // Returns a test result to the embedder.
+ ReturnTestResult(string result);
+};
diff --git a/chromium/headless/lib/headless_browser_browsertest.cc b/chromium/headless/lib/headless_browser_browsertest.cc
index a6bbc8bf52d..77796ffe349 100644
--- a/chromium/headless/lib/headless_browser_browsertest.cc
+++ b/chromium/headless/lib/headless_browser_browsertest.cc
@@ -4,9 +4,13 @@
#include <memory>
+#include "base/command_line.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/test/browser_test.h"
+#include "headless/public/domains/network.h"
#include "headless/public/domains/page.h"
#include "headless/public/headless_browser.h"
#include "headless/public/headless_devtools_client.h"
@@ -14,31 +18,156 @@
#include "headless/public/headless_web_contents.h"
#include "headless/test/headless_browser_test.h"
#include "headless/test/test_protocol_handler.h"
+#include "headless/test/test_url_request_job.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "net/cookies/cookie_store.h"
#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "net/url_request/url_request_context.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/size.h"
+using testing::UnorderedElementsAre;
+
namespace headless {
+IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, CreateAndDestroyBrowserContext) {
+ HeadlessBrowserContext* browser_context =
+ browser()->CreateBrowserContextBuilder().Build();
+
+ EXPECT_THAT(browser()->GetAllBrowserContexts(),
+ UnorderedElementsAre(browser_context));
+
+ browser_context->Close();
+
+ EXPECT_TRUE(browser()->GetAllBrowserContexts().empty());
+}
+
+IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest,
+ CreateAndDoNotDestroyBrowserContext) {
+ HeadlessBrowserContext* browser_context =
+ browser()->CreateBrowserContextBuilder().Build();
+
+ EXPECT_THAT(browser()->GetAllBrowserContexts(),
+ UnorderedElementsAre(browser_context));
+
+ // We check that HeadlessBrowser correctly handles non-closed BrowserContexts.
+ // We can rely on Chromium DCHECKs to capture this.
+}
+
IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, CreateAndDestroyWebContents) {
+ HeadlessBrowserContext* browser_context =
+ browser()->CreateBrowserContextBuilder().Build();
+
HeadlessWebContents* web_contents =
- browser()->CreateWebContentsBuilder().Build();
+ browser_context->CreateWebContentsBuilder().Build();
EXPECT_TRUE(web_contents);
- EXPECT_EQ(static_cast<size_t>(1), browser()->GetAllWebContents().size());
- EXPECT_EQ(web_contents, browser()->GetAllWebContents()[0]);
+ EXPECT_THAT(browser()->GetAllBrowserContexts(),
+ UnorderedElementsAre(browser_context));
+ EXPECT_THAT(browser_context->GetAllWebContents(),
+ UnorderedElementsAre(web_contents));
+
// TODO(skyostil): Verify viewport dimensions once we can.
+
web_contents->Close();
- EXPECT_TRUE(browser()->GetAllWebContents().empty());
+ EXPECT_TRUE(browser_context->GetAllWebContents().empty());
+
+ browser_context->Close();
+
+ EXPECT_TRUE(browser()->GetAllBrowserContexts().empty());
+}
+
+IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest,
+ WebContentsAreDestroyedWithContext) {
+ HeadlessBrowserContext* browser_context =
+ browser()->CreateBrowserContextBuilder().Build();
+
+ HeadlessWebContents* web_contents =
+ browser_context->CreateWebContentsBuilder().Build();
+ EXPECT_TRUE(web_contents);
+
+ EXPECT_THAT(browser()->GetAllBrowserContexts(),
+ UnorderedElementsAre(browser_context));
+ EXPECT_THAT(browser_context->GetAllWebContents(),
+ UnorderedElementsAre(web_contents));
+
+ browser_context->Close();
+
+ EXPECT_TRUE(browser()->GetAllBrowserContexts().empty());
+
+ // If WebContents are not destroyed, Chromium DCHECKs will capture this.
+}
+
+IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, CreateAndDoNotDestroyWebContents) {
+ HeadlessBrowserContext* browser_context =
+ browser()->CreateBrowserContextBuilder().Build();
+
+ HeadlessWebContents* web_contents =
+ browser_context->CreateWebContentsBuilder().Build();
+ EXPECT_TRUE(web_contents);
+
+ EXPECT_THAT(browser()->GetAllBrowserContexts(),
+ UnorderedElementsAre(browser_context));
+ EXPECT_THAT(browser_context->GetAllWebContents(),
+ UnorderedElementsAre(web_contents));
+
+ // If WebContents are not destroyed, Chromium DCHECKs will capture this.
+}
+
+IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, DestroyAndCreateTwoWebContents) {
+ HeadlessBrowserContext* browser_context1 =
+ browser()->CreateBrowserContextBuilder().Build();
+ EXPECT_TRUE(browser_context1);
+ HeadlessWebContents* web_contents1 =
+ browser_context1->CreateWebContentsBuilder().Build();
+ EXPECT_TRUE(web_contents1);
+
+ EXPECT_THAT(browser()->GetAllBrowserContexts(),
+ UnorderedElementsAre(browser_context1));
+ EXPECT_THAT(browser_context1->GetAllWebContents(),
+ UnorderedElementsAre(web_contents1));
+
+ HeadlessBrowserContext* browser_context2 =
+ browser()->CreateBrowserContextBuilder().Build();
+ EXPECT_TRUE(browser_context2);
+ HeadlessWebContents* web_contents2 =
+ browser_context2->CreateWebContentsBuilder().Build();
+ EXPECT_TRUE(web_contents2);
+
+ EXPECT_THAT(browser()->GetAllBrowserContexts(),
+ UnorderedElementsAre(browser_context1, browser_context2));
+ EXPECT_THAT(browser_context1->GetAllWebContents(),
+ UnorderedElementsAre(web_contents1));
+ EXPECT_THAT(browser_context2->GetAllWebContents(),
+ UnorderedElementsAre(web_contents2));
+
+ browser_context1->Close();
+
+ EXPECT_THAT(browser()->GetAllBrowserContexts(),
+ UnorderedElementsAre(browser_context2));
+ EXPECT_THAT(browser_context2->GetAllWebContents(),
+ UnorderedElementsAre(web_contents2));
+
+ browser_context2->Close();
+
+ EXPECT_TRUE(browser()->GetAllBrowserContexts().empty());
}
IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, CreateWithBadURL) {
GURL bad_url("not_valid");
+
+ HeadlessBrowserContext* browser_context =
+ browser()->CreateBrowserContextBuilder().Build();
+
HeadlessWebContents* web_contents =
- browser()->CreateWebContentsBuilder().SetInitialURL(bad_url).Build();
+ browser_context->CreateWebContentsBuilder()
+ .SetInitialURL(bad_url)
+ .Build();
+
EXPECT_FALSE(web_contents);
- EXPECT_TRUE(browser()->GetAllWebContents().empty());
+ EXPECT_TRUE(browser_context->GetAllWebContents().empty());
}
class HeadlessBrowserTestWithProxy : public HeadlessBrowserTest {
@@ -66,42 +195,48 @@ class HeadlessBrowserTestWithProxy : public HeadlessBrowserTest {
};
IN_PROC_BROWSER_TEST_F(HeadlessBrowserTestWithProxy, SetProxyServer) {
- HeadlessBrowser::Options::Builder builder;
- builder.SetProxyServer(proxy_server()->host_port_pair());
- SetBrowserOptions(builder.Build());
+ HeadlessBrowserContext* browser_context =
+ browser()
+ ->CreateBrowserContextBuilder()
+ .SetProxyServer(proxy_server()->host_port_pair())
+ .Build();
// Load a page which doesn't actually exist, but for which the our proxy
// returns valid content anyway.
- //
- // TODO(altimin): Currently this construction does not serve hello.html
- // from headless/test/data as expected. We should fix this.
HeadlessWebContents* web_contents =
- browser()
- ->CreateWebContentsBuilder()
+ browser_context->CreateWebContentsBuilder()
.SetInitialURL(GURL("http://not-an-actual-domain.tld/hello.html"))
.Build();
EXPECT_TRUE(WaitForLoad(web_contents));
- EXPECT_EQ(static_cast<size_t>(1), browser()->GetAllWebContents().size());
- EXPECT_EQ(web_contents, browser()->GetAllWebContents()[0]);
+ EXPECT_THAT(browser()->GetAllBrowserContexts(),
+ UnorderedElementsAre(browser_context));
+ EXPECT_THAT(browser_context->GetAllWebContents(),
+ UnorderedElementsAre(web_contents));
web_contents->Close();
- EXPECT_TRUE(browser()->GetAllWebContents().empty());
+ EXPECT_TRUE(browser_context->GetAllWebContents().empty());
}
IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, SetHostResolverRules) {
EXPECT_TRUE(embedded_test_server()->Start());
- HeadlessBrowser::Options::Builder builder;
- builder.SetHostResolverRules(
+
+ std::string host_resolver_rules =
base::StringPrintf("MAP not-an-actual-domain.tld 127.0.0.1:%d",
- embedded_test_server()->host_port_pair().port()));
- SetBrowserOptions(builder.Build());
+ embedded_test_server()->host_port_pair().port());
+
+ HeadlessBrowserContext* browser_context =
+ browser()
+ ->CreateBrowserContextBuilder()
+ .SetHostResolverRules(host_resolver_rules)
+ .Build();
// Load a page which doesn't actually exist, but which is turned into a valid
// address by our host resolver rules.
HeadlessWebContents* web_contents =
- browser()
- ->CreateWebContentsBuilder()
+ browser_context->CreateWebContentsBuilder()
.SetInitialURL(GURL("http://not-an-actual-domain.tld/hello.html"))
.Build();
+ EXPECT_TRUE(web_contents);
+
EXPECT_TRUE(WaitForLoad(web_contents));
}
@@ -109,19 +244,21 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, HttpProtocolHandler) {
const std::string kResponseBody = "<p>HTTP response body</p>";
ProtocolHandlerMap protocol_handlers;
protocol_handlers[url::kHttpScheme] =
- base::WrapUnique(new TestProtocolHandler(kResponseBody));
+ base::MakeUnique<TestProtocolHandler>(kResponseBody);
- HeadlessBrowser::Options::Builder builder;
- builder.SetProtocolHandlers(std::move(protocol_handlers));
- SetBrowserOptions(builder.Build());
+ HeadlessBrowserContext* browser_context =
+ browser()
+ ->CreateBrowserContextBuilder()
+ .SetProtocolHandlers(std::move(protocol_handlers))
+ .Build();
// Load a page which doesn't actually exist, but which is fetched by our
// custom protocol handler.
HeadlessWebContents* web_contents =
- browser()
- ->CreateWebContentsBuilder()
+ browser_context->CreateWebContentsBuilder()
.SetInitialURL(GURL("http://not-an-actual-domain.tld/hello.html"))
.Build();
+ EXPECT_TRUE(web_contents);
EXPECT_TRUE(WaitForLoad(web_contents));
std::string inner_html;
@@ -136,19 +273,21 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, HttpsProtocolHandler) {
const std::string kResponseBody = "<p>HTTPS response body</p>";
ProtocolHandlerMap protocol_handlers;
protocol_handlers[url::kHttpsScheme] =
- base::WrapUnique(new TestProtocolHandler(kResponseBody));
+ base::MakeUnique<TestProtocolHandler>(kResponseBody);
- HeadlessBrowser::Options::Builder builder;
- builder.SetProtocolHandlers(std::move(protocol_handlers));
- SetBrowserOptions(builder.Build());
+ HeadlessBrowserContext* browser_context =
+ browser()
+ ->CreateBrowserContextBuilder()
+ .SetProtocolHandlers(std::move(protocol_handlers))
+ .Build();
// Load a page which doesn't actually exist, but which is fetched by our
// custom protocol handler.
HeadlessWebContents* web_contents =
- browser()
- ->CreateWebContentsBuilder()
+ browser_context->CreateWebContentsBuilder()
.SetInitialURL(GURL("https://not-an-actual-domain.tld/hello.html"))
.Build();
+ EXPECT_TRUE(web_contents);
EXPECT_TRUE(WaitForLoad(web_contents));
std::string inner_html;
@@ -159,4 +298,348 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, HttpsProtocolHandler) {
EXPECT_EQ(kResponseBody, inner_html);
}
+IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, WebGLSupported) {
+ HeadlessBrowserContext* browser_context =
+ browser()->CreateBrowserContextBuilder().Build();
+
+ HeadlessWebContents* web_contents =
+ browser_context->CreateWebContentsBuilder().Build();
+
+ bool webgl_supported;
+ EXPECT_TRUE(
+ EvaluateScript(web_contents,
+ "(document.createElement('canvas').getContext('webgl')"
+ " instanceof WebGLRenderingContext)")
+ ->GetResult()
+ ->GetValue()
+ ->GetAsBoolean(&webgl_supported));
+ EXPECT_TRUE(webgl_supported);
+}
+
+IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, DefaultSizes) {
+ HeadlessBrowserContext* browser_context =
+ browser()->CreateBrowserContextBuilder().Build();
+
+ HeadlessWebContents* web_contents =
+ browser_context->CreateWebContentsBuilder().Build();
+
+ HeadlessBrowser::Options::Builder builder;
+ const HeadlessBrowser::Options kDefaultOptions = builder.Build();
+
+ int screen_width;
+ int screen_height;
+ int window_width;
+ int window_height;
+
+ EXPECT_TRUE(EvaluateScript(web_contents, "screen.width")
+ ->GetResult()
+ ->GetValue()
+ ->GetAsInteger(&screen_width));
+ EXPECT_TRUE(EvaluateScript(web_contents, "screen.height")
+ ->GetResult()
+ ->GetValue()
+ ->GetAsInteger(&screen_height));
+ EXPECT_TRUE(EvaluateScript(web_contents, "window.innerWidth")
+ ->GetResult()
+ ->GetValue()
+ ->GetAsInteger(&window_width));
+ EXPECT_TRUE(EvaluateScript(web_contents, "window.innerHeight")
+ ->GetResult()
+ ->GetValue()
+ ->GetAsInteger(&window_height));
+
+ EXPECT_EQ(kDefaultOptions.window_size.width(), screen_width);
+ EXPECT_EQ(kDefaultOptions.window_size.height(), screen_height);
+ EXPECT_EQ(kDefaultOptions.window_size.width(), window_width);
+ EXPECT_EQ(kDefaultOptions.window_size.height(), window_height);
+}
+
+namespace {
+
+// True if the request method is "safe" (per section 4.2.1 of RFC 7231).
+bool IsMethodSafe(const std::string& method) {
+ return method == "GET" || method == "HEAD" || method == "OPTIONS" ||
+ method == "TRACE";
+}
+
+class ProtocolHandlerWithCookies
+ : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ ProtocolHandlerWithCookies(net::CookieList* sent_cookies);
+ ~ProtocolHandlerWithCookies() override {}
+
+ net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override;
+
+ private:
+ net::CookieList* sent_cookies_; // Not owned.
+
+ DISALLOW_COPY_AND_ASSIGN(ProtocolHandlerWithCookies);
+};
+
+class URLRequestJobWithCookies : public TestURLRequestJob {
+ public:
+ URLRequestJobWithCookies(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ net::CookieList* sent_cookies);
+ ~URLRequestJobWithCookies() override {}
+
+ // net::URLRequestJob implementation:
+ void Start() override;
+
+ private:
+ void SaveCookiesAndStart(const net::CookieList& cookie_list);
+
+ net::CookieList* sent_cookies_; // Not owned.
+ base::WeakPtrFactory<URLRequestJobWithCookies> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(URLRequestJobWithCookies);
+};
+
+ProtocolHandlerWithCookies::ProtocolHandlerWithCookies(
+ net::CookieList* sent_cookies)
+ : sent_cookies_(sent_cookies) {}
+
+net::URLRequestJob* ProtocolHandlerWithCookies::MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const {
+ return new URLRequestJobWithCookies(request, network_delegate, sent_cookies_);
+}
+
+URLRequestJobWithCookies::URLRequestJobWithCookies(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ net::CookieList* sent_cookies)
+ // Return an empty response for every request.
+ : TestURLRequestJob(request, network_delegate, ""),
+ sent_cookies_(sent_cookies),
+ weak_factory_(this) {}
+
+void URLRequestJobWithCookies::Start() {
+ net::CookieStore* cookie_store = request_->context()->cookie_store();
+ net::CookieOptions options;
+ options.set_include_httponly();
+
+ // See net::URLRequestHttpJob::AddCookieHeaderAndStart().
+ url::Origin requested_origin(request_->url());
+ url::Origin site_for_cookies(request_->first_party_for_cookies());
+
+ if (net::registry_controlled_domains::SameDomainOrHost(
+ requested_origin, site_for_cookies,
+ net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
+ if (net::registry_controlled_domains::SameDomainOrHost(
+ requested_origin, request_->initiator(),
+ net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
+ options.set_same_site_cookie_mode(
+ net::CookieOptions::SameSiteCookieMode::INCLUDE_STRICT_AND_LAX);
+ } else if (IsMethodSafe(request_->method())) {
+ options.set_same_site_cookie_mode(
+ net::CookieOptions::SameSiteCookieMode::INCLUDE_LAX);
+ }
+ }
+ cookie_store->GetCookieListWithOptionsAsync(
+ request_->url(), options,
+ base::Bind(&URLRequestJobWithCookies::SaveCookiesAndStart,
+ weak_factory_.GetWeakPtr()));
+}
+
+void URLRequestJobWithCookies::SaveCookiesAndStart(
+ const net::CookieList& cookie_list) {
+ *sent_cookies_ = cookie_list;
+ NotifyHeadersComplete();
+}
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, ReadCookiesInProtocolHandler) {
+ net::CookieList sent_cookies;
+ ProtocolHandlerMap protocol_handlers;
+ protocol_handlers[url::kHttpsScheme] =
+ base::MakeUnique<ProtocolHandlerWithCookies>(&sent_cookies);
+
+ HeadlessBrowserContext* browser_context =
+ browser()
+ ->CreateBrowserContextBuilder()
+ .SetProtocolHandlers(std::move(protocol_handlers))
+ .Build();
+
+ HeadlessWebContents* web_contents =
+ browser_context->CreateWebContentsBuilder()
+ .SetInitialURL(GURL("https://example.com/cookie.html"))
+ .Build();
+ EXPECT_TRUE(WaitForLoad(web_contents));
+
+ // The first load has no cookies.
+ EXPECT_EQ(0u, sent_cookies.size());
+
+ // Set a cookie and reload the page.
+ EXPECT_FALSE(EvaluateScript(
+ web_contents,
+ "document.cookie = 'shape=oblong', window.location.reload()")
+ ->HasExceptionDetails());
+ EXPECT_TRUE(WaitForLoad(web_contents));
+
+ // We should have sent the cookie this time.
+ EXPECT_EQ(1u, sent_cookies.size());
+ EXPECT_EQ("shape", sent_cookies[0].Name());
+ EXPECT_EQ("oblong", sent_cookies[0].Value());
+}
+
+namespace {
+
+class CookieSetter {
+ public:
+ CookieSetter(HeadlessBrowserTest* browser_test,
+ HeadlessWebContents* web_contents,
+ std::unique_ptr<network::SetCookieParams> set_cookie_params)
+ : browser_test_(browser_test),
+ web_contents_(web_contents),
+ devtools_client_(HeadlessDevToolsClient::Create()) {
+ web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get());
+ devtools_client_->GetNetwork()->GetExperimental()->SetCookie(
+ std::move(set_cookie_params),
+ base::Bind(&CookieSetter::OnSetCookieResult, base::Unretained(this)));
+ }
+
+ ~CookieSetter() {
+ web_contents_->GetDevToolsTarget()->DetachClient(devtools_client_.get());
+ }
+
+ void OnSetCookieResult(std::unique_ptr<network::SetCookieResult> result) {
+ result_ = std::move(result);
+ browser_test_->FinishAsynchronousTest();
+ }
+
+ std::unique_ptr<network::SetCookieResult> TakeResult() {
+ return std::move(result_);
+ }
+
+ private:
+ HeadlessBrowserTest* browser_test_; // Not owned.
+ HeadlessWebContents* web_contents_; // Not owned.
+ std::unique_ptr<HeadlessDevToolsClient> devtools_client_;
+
+ std::unique_ptr<network::SetCookieResult> result_;
+
+ DISALLOW_COPY_AND_ASSIGN(CookieSetter);
+};
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, SetCookiesWithDevTools) {
+ net::CookieList sent_cookies;
+ ProtocolHandlerMap protocol_handlers;
+ protocol_handlers[url::kHttpsScheme] =
+ base::WrapUnique(new ProtocolHandlerWithCookies(&sent_cookies));
+
+ HeadlessBrowserContext* browser_context =
+ browser()
+ ->CreateBrowserContextBuilder()
+ .SetProtocolHandlers(std::move(protocol_handlers))
+ .Build();
+
+ HeadlessWebContents* web_contents =
+ browser_context->CreateWebContentsBuilder()
+ .SetInitialURL(GURL("https://example.com/cookie.html"))
+ .Build();
+ EXPECT_TRUE(WaitForLoad(web_contents));
+
+ // The first load has no cookies.
+ EXPECT_EQ(0u, sent_cookies.size());
+
+ // Set some cookies.
+ {
+ std::unique_ptr<network::SetCookieParams> set_cookie_params =
+ network::SetCookieParams::Builder()
+ .SetUrl("https://example.com")
+ .SetName("shape")
+ .SetValue("oblong")
+ .Build();
+ CookieSetter cookie_setter(this, web_contents,
+ std::move(set_cookie_params));
+ RunAsynchronousTest();
+ std::unique_ptr<network::SetCookieResult> result =
+ cookie_setter.TakeResult();
+ EXPECT_TRUE(result->GetSuccess());
+ }
+ {
+ // Try setting all the fields so we notice if the protocol for any of them
+ // changes.
+ std::unique_ptr<network::SetCookieParams> set_cookie_params =
+ network::SetCookieParams::Builder()
+ .SetUrl("https://other.com")
+ .SetName("shape")
+ .SetValue("trapezoid")
+ .SetDomain("other.com")
+ .SetPath("")
+ .SetSecure(true)
+ .SetHttpOnly(true)
+ .SetSameSite(network::CookieSameSite::STRICT)
+ .SetExpirationDate(0)
+ .Build();
+ CookieSetter cookie_setter(this, web_contents,
+ std::move(set_cookie_params));
+ RunAsynchronousTest();
+ std::unique_ptr<network::SetCookieResult> result =
+ cookie_setter.TakeResult();
+ EXPECT_TRUE(result->GetSuccess());
+ }
+
+ // Reload the page.
+ EXPECT_FALSE(EvaluateScript(web_contents, "window.location.reload();")
+ ->HasExceptionDetails());
+ EXPECT_TRUE(WaitForLoad(web_contents));
+
+ // We should have sent the matching cookies this time.
+ EXPECT_EQ(1u, sent_cookies.size());
+ EXPECT_EQ("shape", sent_cookies[0].Name());
+ EXPECT_EQ("oblong", sent_cookies[0].Value());
+}
+
+// TODO(skyostil): This test currently relies on being able to run a shell
+// script.
+#if defined(OS_POSIX)
+#define MAYBE_RendererCommandPrefixTest RendererCommandPrefixTest
+#else
+#define MAYBE_RendererCommandPrefixTest DISABLED_RendererCommandPrefixTest
+#endif // defined(OS_POSIX)
+IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, MAYBE_RendererCommandPrefixTest) {
+ base::ThreadRestrictions::SetIOAllowed(true);
+ base::FilePath launcher_stamp;
+ base::CreateTemporaryFile(&launcher_stamp);
+
+ base::FilePath launcher_script;
+ FILE* launcher_file = base::CreateAndOpenTemporaryFile(&launcher_script);
+ fprintf(launcher_file, "#!/bin/sh\n");
+ fprintf(launcher_file, "echo $@ > %s\n", launcher_stamp.value().c_str());
+ fprintf(launcher_file, "exec $@\n");
+ fclose(launcher_file);
+ base::SetPosixFilePermissions(launcher_script,
+ base::FILE_PERMISSION_READ_BY_USER |
+ base::FILE_PERMISSION_EXECUTE_BY_USER);
+
+ base::CommandLine::ForCurrentProcess()->AppendSwitch("--no-sandbox");
+ base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ "--renderer-cmd-prefix", launcher_script.value());
+
+ 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));
+
+ // Make sure the launcher was invoked when starting the renderer.
+ std::string stamp;
+ EXPECT_TRUE(base::ReadFileToString(launcher_stamp, &stamp));
+ EXPECT_GE(stamp.find("--type=renderer"), 0u);
+
+ base::DeleteFile(launcher_script, false);
+ base::DeleteFile(launcher_stamp, false);
+}
+
} // namespace headless
diff --git a/chromium/headless/lib/headless_browser_context_browsertest.cc b/chromium/headless/lib/headless_browser_context_browsertest.cc
index 34490bb6284..49ab195c094 100644
--- a/chromium/headless/lib/headless_browser_context_browsertest.cc
+++ b/chromium/headless/lib/headless_browser_context_browsertest.cc
@@ -4,9 +4,14 @@
#include <memory>
+#include "base/files/scoped_temp_dir.h"
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
+#include "base/threading/thread_restrictions.h"
+#include "content/public/browser/render_view_host.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/domains/runtime.h"
#include "headless/public/headless_browser.h"
#include "headless/public/headless_browser_context.h"
@@ -46,7 +51,8 @@ class HeadlessBrowserContextIsolationTest
: public HeadlessAsyncDevTooledBrowserTest {
public:
HeadlessBrowserContextIsolationTest()
- : web_contents2_(nullptr),
+ : browser_context_(nullptr),
+ web_contents2_(nullptr),
devtools_client2_(HeadlessDevToolsClient::Create()) {
EXPECT_TRUE(embedded_test_server()->Start());
}
@@ -55,10 +61,7 @@ class HeadlessBrowserContextIsolationTest
void DevToolsTargetReady() override {
if (!web_contents2_) {
browser_context_ = browser()->CreateBrowserContextBuilder().Build();
- web_contents2_ = browser()
- ->CreateWebContentsBuilder()
- .SetBrowserContext(browser_context_.get())
- .Build();
+ web_contents2_ = browser_context_->CreateWebContentsBuilder().Build();
web_contents2_->AddObserver(this);
return;
}
@@ -143,12 +146,12 @@ class HeadlessBrowserContextIsolationTest
void FinishTest() {
web_contents2_->RemoveObserver(this);
web_contents2_->Close();
- browser_context_.reset();
+ browser_context_->Close();
FinishAsynchronousTest();
}
private:
- std::unique_ptr<HeadlessBrowserContext> browser_context_;
+ HeadlessBrowserContext* browser_context_;
HeadlessWebContents* web_contents2_;
std::unique_ptr<HeadlessDevToolsClient> devtools_client2_;
std::unique_ptr<LoadObserver> load_observer_;
@@ -160,20 +163,18 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, ContextProtocolHandler) {
const std::string kResponseBody = "<p>HTTP response body</p>";
ProtocolHandlerMap protocol_handlers;
protocol_handlers[url::kHttpScheme] =
- base::WrapUnique(new TestProtocolHandler(kResponseBody));
+ base::MakeUnique<TestProtocolHandler>(kResponseBody);
// Load a page which doesn't actually exist, but which is fetched by our
// custom protocol handler.
- std::unique_ptr<HeadlessBrowserContext> browser_context =
+ HeadlessBrowserContext* browser_context =
browser()
->CreateBrowserContextBuilder()
.SetProtocolHandlers(std::move(protocol_handlers))
.Build();
HeadlessWebContents* web_contents =
- browser()
- ->CreateWebContentsBuilder()
+ browser_context->CreateWebContentsBuilder()
.SetInitialURL(GURL("http://not-an-actual-domain.tld/hello.html"))
- .SetBrowserContext(browser_context.get())
.Build();
EXPECT_TRUE(WaitForLoad(web_contents));
@@ -185,12 +186,14 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, ContextProtocolHandler) {
EXPECT_EQ(kResponseBody, inner_html);
web_contents->Close();
- // Loading the same non-existent page using a tab with the default context
+ HeadlessBrowserContext* another_browser_context =
+ browser()->CreateBrowserContextBuilder().Build();
+
+ // Loading the same non-existent page using a tab with a different context
// should not work since the protocol handler only exists on the custom
// context.
web_contents =
- browser()
- ->CreateWebContentsBuilder()
+ another_browser_context->CreateWebContentsBuilder()
.SetInitialURL(GURL("http://not-an-actual-domain.tld/hello.html"))
.Build();
EXPECT_TRUE(WaitForLoad(web_contents));
@@ -202,4 +205,95 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, ContextProtocolHandler) {
web_contents->Close();
}
+IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, UserDataDir) {
+ // We do not want to bother with posting tasks to create a temp dir.
+ // Just allow IO from main thread for now.
+ base::ThreadRestrictions::SetIOAllowed(true);
+
+ EXPECT_TRUE(embedded_test_server()->Start());
+
+ base::ScopedTempDir user_data_dir;
+ ASSERT_TRUE(user_data_dir.CreateUniqueTempDir());
+
+ // Newly created temp directory should be empty.
+ EXPECT_TRUE(base::IsDirectoryEmpty(user_data_dir.GetPath()));
+
+ HeadlessBrowserContext* browser_context =
+ browser()
+ ->CreateBrowserContextBuilder()
+ .SetUserDataDir(user_data_dir.GetPath())
+ .SetIncognitoMode(false)
+ .Build();
+
+ HeadlessWebContents* web_contents =
+ browser_context->CreateWebContentsBuilder()
+ .SetInitialURL(embedded_test_server()->GetURL("/hello.html"))
+ .Build();
+
+ EXPECT_TRUE(WaitForLoad(web_contents));
+
+ // Something should be written to this directory.
+ // If it is not the case, more complex page may be needed.
+ // ServiceWorkers may be a good option.
+ EXPECT_FALSE(base::IsDirectoryEmpty(user_data_dir.GetPath()));
+}
+
+IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, IncognitoMode) {
+ // We do not want to bother with posting tasks to create a temp dir.
+ // Just allow IO from main thread for now.
+ base::ThreadRestrictions::SetIOAllowed(true);
+
+ EXPECT_TRUE(embedded_test_server()->Start());
+
+ base::ScopedTempDir user_data_dir;
+ ASSERT_TRUE(user_data_dir.CreateUniqueTempDir());
+
+ // Newly created temp directory should be empty.
+ EXPECT_TRUE(base::IsDirectoryEmpty(user_data_dir.GetPath()));
+
+ HeadlessBrowserContext* browser_context =
+ browser()
+ ->CreateBrowserContextBuilder()
+ .SetUserDataDir(user_data_dir.GetPath())
+ .SetIncognitoMode(true)
+ .Build();
+
+ HeadlessWebContents* web_contents =
+ browser_context->CreateWebContentsBuilder()
+ .SetInitialURL(embedded_test_server()->GetURL("/hello.html"))
+ .Build();
+
+ EXPECT_TRUE(WaitForLoad(web_contents));
+
+ // Similar to test above, but now we are in incognito mode,
+ // so nothing should be written to this directory.
+ EXPECT_TRUE(base::IsDirectoryEmpty(user_data_dir.GetPath()));
+}
+
+IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, ContextWebPreferences) {
+ // By default, hide_scrollbars should be false.
+ EXPECT_FALSE(WebPreferences().hide_scrollbars);
+
+ // Set hide_scrollbars preference to true for a new BrowserContext.
+ HeadlessBrowserContext* browser_context =
+ browser()
+ ->CreateBrowserContextBuilder()
+ .SetOverrideWebPreferencesCallback(
+ base::Bind([](headless::WebPreferences* preferences) {
+ preferences->hide_scrollbars = true;
+ }))
+ .Build();
+ HeadlessWebContents* web_contents =
+ browser_context->CreateWebContentsBuilder()
+ .SetInitialURL(GURL("about:blank"))
+ .Build();
+
+ // Verify that the preference takes effect.
+ HeadlessWebContentsImpl* contents_impl =
+ HeadlessWebContentsImpl::From(web_contents);
+ EXPECT_TRUE(contents_impl->web_contents()
+ ->GetRenderViewHost()
+ ->GetWebkitPreferences().hide_scrollbars);
+}
+
} // namespace headless
diff --git a/chromium/headless/lib/headless_browsertest_resource_ids b/chromium/headless/lib/headless_browsertest_resource_ids
new file mode 100644
index 00000000000..fad4a66f794
--- /dev/null
+++ b/chromium/headless/lib/headless_browsertest_resource_ids
@@ -0,0 +1,7 @@
+{
+ "SRCDIR": "../..",
+
+ "headless/lib/headless_browsertest_resources.grd": {
+ "includes": [31000],
+ }
+}
diff --git a/chromium/headless/lib/headless_browsertest_resources.grd b/chromium/headless/lib/headless_browsertest_resources.grd
new file mode 100644
index 00000000000..db803c1609d
--- /dev/null
+++ b/chromium/headless/lib/headless_browsertest_resources.grd
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<grit latest_public_release="0" current_release="1">
+ <outputs>
+ <output filename="grit/headless_browsertest_resources.h" type="rc_header">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="headless_browsertest_resources.pak" type="data_package" />
+ </outputs>
+ <translations />
+ <release seq="1">
+ <includes>
+ <include name="IDR_HEADLESS_EMBEDDER_TEST_MOJOM_JS" file="${mojom_root}\headless\lib\embedder_test.mojom.js" use_base_dir="false" type="BINDATA" />
+ </includes>
+ </release>
+</grit>
diff --git a/chromium/headless/lib/headless_content_main_delegate.cc b/chromium/headless/lib/headless_content_main_delegate.cc
index 97d04130d77..193169ff8a0 100644
--- a/chromium/headless/lib/headless_content_main_delegate.cc
+++ b/chromium/headless/lib/headless_content_main_delegate.cc
@@ -15,6 +15,7 @@
#include "headless/lib/renderer/headless_content_renderer_client.h"
#include "headless/lib/utility/headless_content_utility_client.h"
#include "ui/base/resource/resource_bundle.h"
+#include "ui/gl/gl_switches.h"
#include "ui/ozone/public/ozone_switches.h"
namespace headless {
@@ -44,12 +45,19 @@ bool HeadlessContentMainDelegate::BasicStartupComplete(int* exit_code) {
if (browser_->options()->single_process_mode)
command_line->AppendSwitch(switches::kSingleProcess);
+ if (browser_->options()->disable_sandbox)
+ command_line->AppendSwitch(switches::kNoSandbox);
+
// The headless backend is automatically chosen for a headless build, but also
// adding it here allows us to run in a non-headless build too.
command_line->AppendSwitchASCII(switches::kOzonePlatform, "headless");
- // TODO(skyostil): Investigate using Mesa/SwiftShader for output.
- command_line->AppendSwitch(switches::kDisableGpu);
+ if (!browser_->options()->gl_implementation.empty()) {
+ command_line->AppendSwitchASCII(switches::kUseGL,
+ browser_->options()->gl_implementation);
+ } else {
+ command_line->AppendSwitch(switches::kDisableGpu);
+ }
SetContentClient(&content_client_);
return false;
diff --git a/chromium/headless/lib/headless_devtools_client_browsertest.cc b/chromium/headless/lib/headless_devtools_client_browsertest.cc
index 8b51fecac51..5188e74c404 100644
--- a/chromium/headless/lib/headless_devtools_client_browsertest.cc
+++ b/chromium/headless/lib/headless_devtools_client_browsertest.cc
@@ -4,18 +4,48 @@
#include <memory>
+#include "base/json/json_reader.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/domains/browser.h"
+#include "headless/public/domains/emulation.h"
#include "headless/public/domains/network.h"
#include "headless/public/domains/page.h"
#include "headless/public/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/test/headless_browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
+#define EXPECT_SIZE_EQ(expected, actual) \
+ do { \
+ EXPECT_EQ((expected).width(), (actual).width()); \
+ EXPECT_EQ((expected).height(), (actual).height()); \
+ } while (false)
+
namespace headless {
+namespace {
+
+std::vector<HeadlessWebContents*> GetAllWebContents(HeadlessBrowser* browser) {
+ std::vector<HeadlessWebContents*> result;
+
+ for (HeadlessBrowserContext* browser_context :
+ browser->GetAllBrowserContexts()) {
+ std::vector<HeadlessWebContents*> web_contents =
+ browser_context->GetAllWebContents();
+ result.insert(result.end(), web_contents.begin(), web_contents.end());
+ }
+
+ return result;
+}
+
+} // namespace
+
class HeadlessDevToolsClientNavigationTest
: public HeadlessAsyncDevTooledBrowserTest,
page::ExperimentalObserver {
@@ -155,8 +185,17 @@ class HeadlessDevToolsClientExperimentalTest
void RunDevTooledTest() override {
EXPECT_TRUE(embedded_test_server()->Start());
// Check that experimental commands require parameter objects.
- devtools_client_->GetRuntime()->GetExperimental()->Run(
- runtime::RunParams::Builder().Build());
+ devtools_client_->GetRuntime()
+ ->GetExperimental()
+ ->SetCustomObjectFormatterEnabled(
+ runtime::SetCustomObjectFormatterEnabledParams::Builder()
+ .SetEnabled(false)
+ .Build());
+
+ // Check that a previously experimental command which takes no parameters
+ // still works by giving it a parameter object.
+ devtools_client_->GetRuntime()->GetExperimental()->RunIfWaitingForDebugger(
+ runtime::RunIfWaitingForDebuggerParams::Builder().Build());
devtools_client_->GetPage()->GetExperimental()->AddObserver(this);
devtools_client_->GetPage()->Enable();
@@ -166,10 +205,452 @@ class HeadlessDevToolsClientExperimentalTest
void OnFrameStoppedLoading(
const page::FrameStoppedLoadingParams& params) override {
- FinishAsynchronousTest();
+ // Check that a non-experimental command which has no return value can be
+ // called with a void() callback.
+ devtools_client_->GetPage()->Reload(
+ page::ReloadParams::Builder().Build(),
+ base::Bind(&HeadlessDevToolsClientExperimentalTest::OnReloadStarted,
+ base::Unretained(this)));
}
+
+ void OnReloadStarted() { FinishAsynchronousTest(); }
};
HEADLESS_ASYNC_DEVTOOLED_TEST_F(HeadlessDevToolsClientExperimentalTest);
+class BrowserDomainCreateAndDeletePageTest
+ : public HeadlessAsyncDevTooledBrowserTest {
+ void RunDevTooledTest() override {
+ EXPECT_TRUE(embedded_test_server()->Start());
+
+ EXPECT_EQ(1u, GetAllWebContents(browser()).size());
+
+ devtools_client_->GetBrowser()->GetExperimental()->CreateTarget(
+ browser::CreateTargetParams::Builder()
+ .SetUrl(embedded_test_server()->GetURL("/hello.html").spec())
+ .SetWidth(1)
+ .SetHeight(1)
+ .Build(),
+ base::Bind(&BrowserDomainCreateAndDeletePageTest::OnCreateTargetResult,
+ base::Unretained(this)));
+ }
+
+ void OnCreateTargetResult(
+ std::unique_ptr<browser::CreateTargetResult> result) {
+ EXPECT_EQ(2u, GetAllWebContents(browser()).size());
+
+ HeadlessWebContentsImpl* contents = HeadlessWebContentsImpl::From(
+ browser()->GetWebContentsForDevToolsAgentHostId(result->GetTargetId()));
+ EXPECT_SIZE_EQ(gfx::Size(1, 1), contents->web_contents()
+ ->GetRenderWidgetHostView()
+ ->GetViewBounds()
+ .size());
+
+ devtools_client_->GetBrowser()->GetExperimental()->CloseTarget(
+ browser::CloseTargetParams::Builder()
+ .SetTargetId(result->GetTargetId())
+ .Build(),
+ base::Bind(&BrowserDomainCreateAndDeletePageTest::OnCloseTargetResult,
+ base::Unretained(this)));
+ }
+
+ void OnCloseTargetResult(std::unique_ptr<browser::CloseTargetResult> result) {
+ EXPECT_TRUE(result->GetSuccess());
+ EXPECT_EQ(1u, GetAllWebContents(browser()).size());
+ FinishAsynchronousTest();
+ }
+};
+
+HEADLESS_ASYNC_DEVTOOLED_TEST_F(BrowserDomainCreateAndDeletePageTest);
+
+class BrowserDomainCreateAndDeleteBrowserContextTest
+ : public HeadlessAsyncDevTooledBrowserTest {
+ void RunDevTooledTest() override {
+ EXPECT_TRUE(embedded_test_server()->Start());
+
+ EXPECT_EQ(1u, GetAllWebContents(browser()).size());
+
+ devtools_client_->GetBrowser()->GetExperimental()->CreateBrowserContext(
+ browser::CreateBrowserContextParams::Builder().Build(),
+ base::Bind(&BrowserDomainCreateAndDeleteBrowserContextTest::
+ OnCreateContextResult,
+ base::Unretained(this)));
+ }
+
+ void OnCreateContextResult(
+ std::unique_ptr<browser::CreateBrowserContextResult> result) {
+ browser_context_id_ = result->GetBrowserContextId();
+
+ devtools_client_->GetBrowser()->GetExperimental()->CreateTarget(
+ browser::CreateTargetParams::Builder()
+ .SetUrl(embedded_test_server()->GetURL("/hello.html").spec())
+ .SetBrowserContextId(result->GetBrowserContextId())
+ .SetWidth(1)
+ .SetHeight(1)
+ .Build(),
+ base::Bind(&BrowserDomainCreateAndDeleteBrowserContextTest::
+ OnCreateTargetResult,
+ base::Unretained(this)));
+ }
+
+ void OnCreateTargetResult(
+ std::unique_ptr<browser::CreateTargetResult> result) {
+ EXPECT_EQ(2u, GetAllWebContents(browser()).size());
+
+ devtools_client_->GetBrowser()->GetExperimental()->CloseTarget(
+ browser::CloseTargetParams::Builder()
+ .SetTargetId(result->GetTargetId())
+ .Build(),
+ base::Bind(&BrowserDomainCreateAndDeleteBrowserContextTest::
+ OnCloseTargetResult,
+ base::Unretained(this)));
+ }
+
+ void OnCloseTargetResult(std::unique_ptr<browser::CloseTargetResult> result) {
+ EXPECT_EQ(1u, GetAllWebContents(browser()).size());
+ EXPECT_TRUE(result->GetSuccess());
+
+ devtools_client_->GetBrowser()->GetExperimental()->DisposeBrowserContext(
+ browser::DisposeBrowserContextParams::Builder()
+ .SetBrowserContextId(browser_context_id_)
+ .Build(),
+ base::Bind(&BrowserDomainCreateAndDeleteBrowserContextTest::
+ OnDisposeBrowserContextResult,
+ base::Unretained(this)));
+ }
+
+ void OnDisposeBrowserContextResult(
+ std::unique_ptr<browser::DisposeBrowserContextResult> result) {
+ EXPECT_TRUE(result->GetSuccess());
+ FinishAsynchronousTest();
+ }
+
+ private:
+ std::string browser_context_id_;
+};
+
+HEADLESS_ASYNC_DEVTOOLED_TEST_F(BrowserDomainCreateAndDeleteBrowserContextTest);
+
+class BrowserDomainDisposeContextFailsIfInUse
+ : public HeadlessAsyncDevTooledBrowserTest {
+ void RunDevTooledTest() override {
+ EXPECT_TRUE(embedded_test_server()->Start());
+
+ EXPECT_EQ(1u, GetAllWebContents(browser()).size());
+ devtools_client_->GetBrowser()->GetExperimental()->CreateBrowserContext(
+ browser::CreateBrowserContextParams::Builder().Build(),
+ base::Bind(&BrowserDomainDisposeContextFailsIfInUse::OnContextCreated,
+ base::Unretained(this)));
+ }
+
+ void OnContextCreated(
+ std::unique_ptr<browser::CreateBrowserContextResult> result) {
+ context_id_ = result->GetBrowserContextId();
+
+ devtools_client_->GetBrowser()->GetExperimental()->CreateTarget(
+ browser::CreateTargetParams::Builder()
+ .SetUrl(embedded_test_server()->GetURL("/hello.html").spec())
+ .SetBrowserContextId(context_id_)
+ .Build(),
+ base::Bind(
+ &BrowserDomainDisposeContextFailsIfInUse::OnCreateTargetResult,
+ base::Unretained(this)));
+ }
+
+ void OnCreateTargetResult(
+ std::unique_ptr<browser::CreateTargetResult> result) {
+ page_id_ = result->GetTargetId();
+
+ devtools_client_->GetBrowser()->GetExperimental()->DisposeBrowserContext(
+ browser::DisposeBrowserContextParams::Builder()
+ .SetBrowserContextId(context_id_)
+ .Build(),
+ base::Bind(&BrowserDomainDisposeContextFailsIfInUse::
+ OnDisposeBrowserContextResult,
+ base::Unretained(this)));
+ }
+
+ void OnDisposeBrowserContextResult(
+ std::unique_ptr<browser::DisposeBrowserContextResult> result) {
+ EXPECT_FALSE(result->GetSuccess());
+
+ // Close the page and try again.
+ devtools_client_->GetBrowser()->GetExperimental()->CloseTarget(
+ browser::CloseTargetParams::Builder().SetTargetId(page_id_).Build(),
+ base::Bind(
+ &BrowserDomainDisposeContextFailsIfInUse::OnCloseTargetResult,
+ base::Unretained(this)));
+ }
+
+ void OnCloseTargetResult(std::unique_ptr<browser::CloseTargetResult> result) {
+ EXPECT_TRUE(result->GetSuccess());
+
+ devtools_client_->GetBrowser()->GetExperimental()->DisposeBrowserContext(
+ browser::DisposeBrowserContextParams::Builder()
+ .SetBrowserContextId(context_id_)
+ .Build(),
+ base::Bind(&BrowserDomainDisposeContextFailsIfInUse::
+ OnDisposeBrowserContextResult2,
+ base::Unretained(this)));
+ }
+
+ void OnDisposeBrowserContextResult2(
+ std::unique_ptr<browser::DisposeBrowserContextResult> result) {
+ EXPECT_TRUE(result->GetSuccess());
+ FinishAsynchronousTest();
+ }
+
+ private:
+ std::string context_id_;
+ std::string page_id_;
+};
+
+HEADLESS_ASYNC_DEVTOOLED_TEST_F(BrowserDomainDisposeContextFailsIfInUse);
+
+class BrowserDomainCreateTwoContexts : public HeadlessAsyncDevTooledBrowserTest,
+ public browser::ExperimentalObserver {
+ public:
+ void RunDevTooledTest() override {
+ EXPECT_TRUE(embedded_test_server()->Start());
+
+ devtools_client_->GetBrowser()->GetExperimental()->AddObserver(this);
+ devtools_client_->GetBrowser()->GetExperimental()->CreateBrowserContext(
+ browser::CreateBrowserContextParams::Builder().Build(),
+ base::Bind(&BrowserDomainCreateTwoContexts::OnContextOneCreated,
+ base::Unretained(this)));
+
+ devtools_client_->GetBrowser()->GetExperimental()->CreateBrowserContext(
+ browser::CreateBrowserContextParams::Builder().Build(),
+ base::Bind(&BrowserDomainCreateTwoContexts::OnContextTwoCreated,
+ base::Unretained(this)));
+ }
+
+ void OnContextOneCreated(
+ std::unique_ptr<browser::CreateBrowserContextResult> result) {
+ context_id_one_ = result->GetBrowserContextId();
+ MaybeCreatePages();
+ }
+
+ void OnContextTwoCreated(
+ std::unique_ptr<browser::CreateBrowserContextResult> result) {
+ context_id_two_ = result->GetBrowserContextId();
+ MaybeCreatePages();
+ }
+
+ void MaybeCreatePages() {
+ if (context_id_one_.empty() || context_id_two_.empty())
+ return;
+
+ devtools_client_->GetBrowser()->GetExperimental()->CreateTarget(
+ browser::CreateTargetParams::Builder()
+ .SetUrl(embedded_test_server()->GetURL("/hello.html").spec())
+ .SetBrowserContextId(context_id_one_)
+ .Build(),
+ base::Bind(&BrowserDomainCreateTwoContexts::OnCreateTargetOneResult,
+ base::Unretained(this)));
+
+ devtools_client_->GetBrowser()->GetExperimental()->CreateTarget(
+ browser::CreateTargetParams::Builder()
+ .SetUrl(embedded_test_server()->GetURL("/hello.html").spec())
+ .SetBrowserContextId(context_id_two_)
+ .Build(),
+ base::Bind(&BrowserDomainCreateTwoContexts::OnCreateTargetTwoResult,
+ base::Unretained(this)));
+ }
+
+ void OnCreateTargetOneResult(
+ std::unique_ptr<browser::CreateTargetResult> result) {
+ page_id_one_ = result->GetTargetId();
+ MaybeTestIsolation();
+ }
+
+ void OnCreateTargetTwoResult(
+ std::unique_ptr<browser::CreateTargetResult> result) {
+ page_id_two_ = result->GetTargetId();
+ MaybeTestIsolation();
+ }
+
+ void MaybeTestIsolation() {
+ if (page_id_one_.empty() || page_id_two_.empty())
+ return;
+
+ devtools_client_->GetBrowser()->GetExperimental()->Attach(
+ browser::AttachParams::Builder().SetTargetId(page_id_one_).Build(),
+ base::Bind(&BrowserDomainCreateTwoContexts::OnAttachedOne,
+ base::Unretained(this)));
+
+ devtools_client_->GetBrowser()->GetExperimental()->Attach(
+ browser::AttachParams::Builder().SetTargetId(page_id_two_).Build(),
+ base::Bind(&BrowserDomainCreateTwoContexts::OnAttachedTwo,
+ base::Unretained(this)));
+ }
+
+ void OnAttachedOne(std::unique_ptr<browser::AttachResult> result) {
+ devtools_client_->GetBrowser()->GetExperimental()->SendMessage(
+ browser::SendMessageParams::Builder()
+ .SetTargetId(page_id_one_)
+ .SetMessage("{\"id\":101, \"method\": \"Page.enable\"}")
+ .Build());
+ }
+
+ void OnAttachedTwo(std::unique_ptr<browser::AttachResult> result) {
+ devtools_client_->GetBrowser()->GetExperimental()->SendMessage(
+ browser::SendMessageParams::Builder()
+ .SetTargetId(page_id_two_)
+ .SetMessage("{\"id\":102, \"method\": \"Page.enable\"}")
+ .Build());
+ }
+
+ void MaybeSetCookieOnPageOne() {
+ if (!page_one_loaded_ || !page_two_loaded_)
+ return;
+
+ devtools_client_->GetBrowser()->GetExperimental()->SendMessage(
+ browser::SendMessageParams::Builder()
+ .SetTargetId(page_id_one_)
+ .SetMessage("{\"id\":201, \"method\": \"Runtime.evaluate\", "
+ "\"params\": {\"expression\": "
+ "\"document.cookie = 'foo=bar';\"}}")
+ .Build());
+ }
+
+ void OnDispatchMessage(
+ const browser::DispatchMessageParams& params) override {
+ std::unique_ptr<base::Value> message =
+ base::JSONReader::Read(params.GetMessage(), base::JSON_PARSE_RFC);
+ const base::DictionaryValue* message_dict;
+ if (!message || !message->GetAsDictionary(&message_dict)) {
+ return;
+ }
+ std::string method;
+ if (message_dict->GetString("method", &method) &&
+ method == "Page.loadEventFired") {
+ if (params.GetTargetId() == page_id_one_) {
+ page_one_loaded_ = true;
+ } else if (params.GetTargetId() == page_id_two_) {
+ page_two_loaded_ = true;
+ }
+ MaybeSetCookieOnPageOne();
+ return;
+ }
+ const base::DictionaryValue* result_dict;
+ if (message_dict->GetDictionary("result", &result_dict)) {
+ // There's a nested result. We want the inner one.
+ if (!result_dict->GetDictionary("result", &result_dict))
+ return;
+ std::string value;
+ if (params.GetTargetId() == page_id_one_) {
+ // TODO(alexclarke): Make some better bindings for Browser.sendMessage.
+ devtools_client_->GetBrowser()->GetExperimental()->SendMessage(
+ browser::SendMessageParams::Builder()
+ .SetTargetId(page_id_two_)
+ .SetMessage("{\"id\":202, \"method\": \"Runtime.evaluate\", "
+ "\"params\": {\"expression\": "
+ "\"document.cookie;\"}}")
+ .Build());
+ } else if (params.GetTargetId() == page_id_two_ &&
+ result_dict->GetString("value", &value)) {
+ EXPECT_EQ("", value) << "Page 2 should not share cookies from page one";
+
+ devtools_client_->GetBrowser()->GetExperimental()->CloseTarget(
+ browser::CloseTargetParams::Builder()
+ .SetTargetId(page_id_one_)
+ .Build(),
+ base::Bind(&BrowserDomainCreateTwoContexts::OnCloseTarget,
+ base::Unretained(this)));
+
+ devtools_client_->GetBrowser()->GetExperimental()->CloseTarget(
+ browser::CloseTargetParams::Builder()
+ .SetTargetId(page_id_two_)
+ .Build(),
+ base::Bind(&BrowserDomainCreateTwoContexts::OnCloseTarget,
+ base::Unretained(this)));
+
+ devtools_client_->GetBrowser()->GetExperimental()->RemoveObserver(this);
+ }
+ }
+ }
+
+ void OnCloseTarget(std::unique_ptr<browser::CloseTargetResult> result) {
+ page_close_count_++;
+
+ if (page_close_count_ < 2)
+ return;
+
+ devtools_client_->GetBrowser()->GetExperimental()->DisposeBrowserContext(
+ browser::DisposeBrowserContextParams::Builder()
+ .SetBrowserContextId(context_id_one_)
+ .Build(),
+ base::Bind(&BrowserDomainCreateTwoContexts::OnCloseContext,
+ base::Unretained(this)));
+
+ devtools_client_->GetBrowser()->GetExperimental()->DisposeBrowserContext(
+ browser::DisposeBrowserContextParams::Builder()
+ .SetBrowserContextId(context_id_two_)
+ .Build(),
+ base::Bind(&BrowserDomainCreateTwoContexts::OnCloseContext,
+ base::Unretained(this)));
+ }
+
+ void OnCloseContext(
+ std::unique_ptr<browser::DisposeBrowserContextResult> result) {
+ EXPECT_TRUE(result->GetSuccess());
+ if (++context_closed_count_ < 2)
+ return;
+
+ FinishAsynchronousTest();
+ }
+
+ private:
+ std::string context_id_one_;
+ std::string context_id_two_;
+ std::string page_id_one_;
+ std::string page_id_two_;
+ bool page_one_loaded_ = false;
+ bool page_two_loaded_ = false;
+ int page_close_count_ = 0;
+ int context_closed_count_ = 0;
+};
+
+HEADLESS_ASYNC_DEVTOOLED_TEST_F(BrowserDomainCreateTwoContexts);
+
+class HeadlessDevToolsNavigationControlTest
+ : public HeadlessAsyncDevTooledBrowserTest,
+ page::ExperimentalObserver {
+ public:
+ void RunDevTooledTest() override {
+ EXPECT_TRUE(embedded_test_server()->Start());
+ devtools_client_->GetPage()->GetExperimental()->AddObserver(this);
+ devtools_client_->GetPage()->Enable();
+ devtools_client_->GetPage()->GetExperimental()->SetControlNavigations(
+ headless::page::SetControlNavigationsParams::Builder()
+ .SetEnabled(true)
+ .Build());
+ devtools_client_->GetPage()->Navigate(
+ embedded_test_server()->GetURL("/hello.html").spec());
+ }
+
+ void OnNavigationRequested(
+ const headless::page::NavigationRequestedParams& params) override {
+ navigation_requested_ = true;
+ // Allow the navigation to proceed.
+ devtools_client_->GetPage()->GetExperimental()->ProcessNavigation(
+ headless::page::ProcessNavigationParams::Builder()
+ .SetNavigationId(params.GetNavigationId())
+ .SetResponse(headless::page::NavigationResponse::PROCEED)
+ .Build());
+ }
+
+ void OnFrameStoppedLoading(
+ const page::FrameStoppedLoadingParams& params) override {
+ EXPECT_TRUE(navigation_requested_);
+ FinishAsynchronousTest();
+ }
+
+ private:
+ bool navigation_requested_ = false;
+};
+
+HEADLESS_ASYNC_DEVTOOLED_TEST_F(HeadlessDevToolsNavigationControlTest);
+
} // namespace headless
diff --git a/chromium/headless/lib/headless_web_contents_browsertest.cc b/chromium/headless/lib/headless_web_contents_browsertest.cc
index 88700d95c23..0fd928371ed 100644
--- a/chromium/headless/lib/headless_web_contents_browsertest.cc
+++ b/chromium/headless/lib/headless_web_contents_browsertest.cc
@@ -12,10 +12,13 @@
#include "headless/public/headless_devtools_client.h"
#include "headless/public/headless_web_contents.h"
#include "headless/test/headless_browser_test.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/size.h"
#include "url/gurl.h"
+using testing::UnorderedElementsAre;
+
namespace headless {
class HeadlessWebContentsTest : public HeadlessBrowserTest {};
@@ -23,34 +26,33 @@ class HeadlessWebContentsTest : public HeadlessBrowserTest {};
IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, Navigation) {
EXPECT_TRUE(embedded_test_server()->Start());
+ HeadlessBrowserContext* browser_context =
+ browser()->CreateBrowserContextBuilder().Build();
+
HeadlessWebContents* web_contents =
- browser()
- ->CreateWebContentsBuilder()
+ browser_context->CreateWebContentsBuilder()
.SetInitialURL(embedded_test_server()->GetURL("/hello.html"))
.Build();
EXPECT_TRUE(WaitForLoad(web_contents));
- std::vector<HeadlessWebContents*> all_web_contents =
- browser()->GetAllWebContents();
-
- EXPECT_EQ(static_cast<size_t>(1), all_web_contents.size());
- EXPECT_EQ(web_contents, all_web_contents[0]);
+ EXPECT_THAT(browser_context->GetAllWebContents(),
+ UnorderedElementsAre(web_contents));
}
IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, WindowOpen) {
EXPECT_TRUE(embedded_test_server()->Start());
+ HeadlessBrowserContext* browser_context =
+ browser()->CreateBrowserContextBuilder().Build();
+
HeadlessWebContents* web_contents =
- browser()
- ->CreateWebContentsBuilder()
+ browser_context->CreateWebContentsBuilder()
.SetInitialURL(embedded_test_server()->GetURL("/window_open.html"))
.Build();
EXPECT_TRUE(WaitForLoad(web_contents));
- std::vector<HeadlessWebContents*> all_web_contents =
- browser()->GetAllWebContents();
-
- EXPECT_EQ(static_cast<size_t>(2), all_web_contents.size());
+ EXPECT_EQ(static_cast<size_t>(2),
+ browser_context->GetAllWebContents().size());
}
class HeadlessWebContentsScreenshotTest
diff --git a/chromium/headless/lib/renderer/headless_content_renderer_client.cc b/chromium/headless/lib/renderer/headless_content_renderer_client.cc
index 5347d609222..82faf2165a5 100644
--- a/chromium/headless/lib/renderer/headless_content_renderer_client.cc
+++ b/chromium/headless/lib/renderer/headless_content_renderer_client.cc
@@ -4,6 +4,9 @@
#include "headless/lib/renderer/headless_content_renderer_client.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/renderer/render_frame.h"
+
namespace headless {
HeadlessContentRendererClient::HeadlessContentRendererClient() {}
diff --git a/chromium/headless/lib/resources/devtools_discovery_page.html b/chromium/headless/lib/resources/devtools_discovery_page.html
index 47534f7c462..86809b9d1c5 100644
--- a/chromium/headless/lib/resources/devtools_discovery_page.html
+++ b/chromium/headless/lib/resources/devtools_discovery_page.html
@@ -5,49 +5,49 @@
</style>
<script>
-function onLoad() {
- var tabs_list_request = new XMLHttpRequest();
- tabs_list_request.open("GET", "/json/list?t=" + new Date().getTime(), true);
- tabs_list_request.onreadystatechange = onReady;
- tabs_list_request.send();
+const fetchjson = (url) => fetch(url).then(r => r.json());
+
+function loadData() {
+ const getList = fetchjson("/json/list");
+ const getVersion = fetchjson('/json/version');
+ Promise.all([getList, getVersion]).then(parseResults);
}
-function onReady() {
- if(this.readyState == 4 && this.status == 200) {
- if(this.response != null)
- var responseJSON = JSON.parse(this.response);
- for (var i = 0; i < responseJSON.length; ++i)
- appendItem(responseJSON[i]);
- }
+function parseResults([listData, versionData]){
+ const version = versionData['WebKit-Version'];
+ const hash = version.match(/\s\(@(\b[0-9a-f]{5,40}\b)/)[1];
+ listData.forEach(item => appendItem(item, hash));
}
-function appendItem(item_object) {
- var frontend_ref;
- if (item_object.devtoolsFrontendUrl) {
- frontend_ref = document.createElement("a");
- frontend_ref.href = item_object.devtoolsFrontendUrl;
- frontend_ref.title = item_object.title;
+function appendItem(item, hash) {
+ let link;
+ if (item.devtoolsFrontendUrl) {
+ link = document.createElement("a");
+ var devtoolsFrontendUrl = item.devtoolsFrontendUrl.replace(/^\/devtools\//,'');
+ link.href = `https://chrome-devtools-frontend.appspot.com/serve_file/@${hash}/${devtoolsFrontendUrl}&remoteFrontend=true`;
+ link.title = item.title;
} else {
- frontend_ref = document.createElement("div");
- frontend_ref.title = "The tab already has active debugging session";
+ link = document.createElement("div");
+ link.title = "The tab already has active debugging session";
}
var text = document.createElement("div");
- if (item_object.title)
- text.innerText = item_object.title;
+ if (item.title)
+ text.textContent = item.title;
else
- text.innerText = "(untitled tab)";
- text.style.cssText = "background-image:url(" + item_object.faviconUrl + ")";
- frontend_ref.appendChild(text);
+ text.textContent = "(untitled tab)";
+ if (item.faviconUrl)
+ text.style.cssText = "background-image:url(" + item.faviconUrl + ")";
+ link.appendChild(text);
- var item = document.createElement("p");
- item.appendChild(frontend_ref);
+ var p = document.createElement("p");
+ p.appendChild(link);
- document.getElementById("items").appendChild(item);
+ document.getElementById("items").appendChild(p);
}
</script>
</head>
-<body onload='onLoad()'>
+<body onload='loadData()'>
<div id='caption'>Inspectable WebContents</div>
<div id='items'></div>
</body>
diff --git a/chromium/headless/lib/resources/headless_lib_resources.grd b/chromium/headless/lib/resources/headless_lib_resources.grd
index 060ab47b9b2..3137ec64834 100644
--- a/chromium/headless/lib/resources/headless_lib_resources.grd
+++ b/chromium/headless/lib/resources/headless_lib_resources.grd
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-
-<grit latest_public_release="0" current_release="1">
+<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
<outputs>
<output filename="grit/headless_lib_resources.h" type="rc_header">
<emit emit_type='prepend'></emit>
diff --git a/chromium/headless/public/domains/types_unittest.cc b/chromium/headless/public/domains/types_unittest.cc
index c665d95b225..bf59b01151a 100644
--- a/chromium/headless/public/domains/types_unittest.cc
+++ b/chromium/headless/public/domains/types_unittest.cc
@@ -9,23 +9,23 @@
namespace headless {
TEST(TypesTest, IntegerProperty) {
- std::unique_ptr<accessibility::GetAXNodeParams> object(
- accessibility::GetAXNodeParams::Builder().SetNodeId(123).Build());
+ std::unique_ptr<page::NavigateToHistoryEntryParams> object(
+ page::NavigateToHistoryEntryParams::Builder().SetEntryId(123).Build());
EXPECT_TRUE(object);
- EXPECT_EQ(123, object->GetNodeId());
+ EXPECT_EQ(123, object->GetEntryId());
- std::unique_ptr<accessibility::GetAXNodeParams> clone(object->Clone());
+ std::unique_ptr<page::NavigateToHistoryEntryParams> clone(object->Clone());
EXPECT_TRUE(clone);
- EXPECT_EQ(123, clone->GetNodeId());
+ EXPECT_EQ(123, clone->GetEntryId());
}
TEST(TypesTest, IntegerPropertyParseError) {
- const char* json = "{\"nodeId\": \"foo\"}";
+ const char* json = "{\"entryId\": \"foo\"}";
std::unique_ptr<base::Value> object = base::JSONReader::Read(json);
EXPECT_TRUE(object);
ErrorReporter errors;
- EXPECT_FALSE(accessibility::GetAXNodeParams::Parse(*object, &errors));
+ EXPECT_FALSE(page::NavigateToHistoryEntryParams::Parse(*object, &errors));
EXPECT_TRUE(errors.HasErrors());
}
diff --git a/chromium/headless/public/headless_browser.cc b/chromium/headless/public/headless_browser.cc
index b1e5dbcc026..cbfbc5e400e 100644
--- a/chromium/headless/public/headless_browser.cc
+++ b/chromium/headless/public/headless_browser.cc
@@ -2,9 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "content/public/common/user_agent.h"
#include "headless/public/headless_browser.h"
+#include <utility>
+
+#include "content/public/common/user_agent.h"
+
using Options = headless::HeadlessBrowser::Options;
using Builder = headless::HeadlessBrowser::Options::Builder;
@@ -13,14 +16,19 @@ namespace headless {
// Product name for building the default user agent string.
namespace {
const char kProductName[] = "HeadlessChrome";
+constexpr gfx::Size kDefaultWindowSize(800, 600);
}
Options::Options(int argc, const char** argv)
: argc(argc),
argv(argv),
- user_agent(content::BuildUserAgentFromProduct(kProductName)),
message_pump(nullptr),
- single_process_mode(false) {}
+ single_process_mode(false),
+ disable_sandbox(false),
+ gl_implementation("osmesa"),
+ user_agent(content::BuildUserAgentFromProduct(kProductName)),
+ window_size(kDefaultWindowSize),
+ incognito_mode(true) {}
Options::Options(Options&& options) = default;
@@ -64,8 +72,28 @@ Builder& Builder::SetSingleProcessMode(bool single_process_mode) {
return *this;
}
-Builder& Builder::SetProtocolHandlers(ProtocolHandlerMap protocol_handlers) {
- options_.protocol_handlers = std::move(protocol_handlers);
+Builder& Builder::SetDisableSandbox(bool disable_sandbox) {
+ options_.disable_sandbox = disable_sandbox;
+ return *this;
+}
+
+Builder& Builder::SetGLImplementation(const std::string& gl_implementation) {
+ options_.gl_implementation = gl_implementation;
+ return *this;
+}
+
+Builder& Builder::SetUserDataDir(const base::FilePath& user_data_dir) {
+ options_.user_data_dir = user_data_dir;
+ return *this;
+}
+
+Builder& Builder::SetWindowSize(const gfx::Size& window_size) {
+ options_.window_size = window_size;
+ return *this;
+}
+
+Builder& Builder::SetIncognitoMode(bool incognito_mode) {
+ options_.incognito_mode = incognito_mode;
return *this;
}
diff --git a/chromium/headless/public/headless_browser.h b/chromium/headless/public/headless_browser.h
index 2e9145153ae..954b79e0db4 100644
--- a/chromium/headless/public/headless_browser.h
+++ b/chromium/headless/public/headless_browser.h
@@ -11,6 +11,7 @@
#include <vector>
#include "base/callback.h"
+#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "headless/public/headless_browser_context.h"
@@ -18,16 +19,13 @@
#include "headless/public/headless_web_contents.h"
#include "net/base/host_port_pair.h"
#include "net/base/ip_endpoint.h"
+#include "ui/gfx/geometry/size.h"
namespace base {
class MessagePump;
class SingleThreadTaskRunner;
}
-namespace gfx {
-class Size;
-}
-
namespace headless {
// This class represents the global headless browser instance. To get a pointer
@@ -38,32 +36,43 @@ class HEADLESS_EXPORT HeadlessBrowser {
public:
struct Options;
- // Open a new tab. Returns a builder object which can be used to set
- // properties for the new tab.
- virtual HeadlessWebContents::Builder CreateWebContentsBuilder() = 0;
+ // Create a new browser context which can be used to create tabs and isolate
+ // them from one another.
+ // Pointer to HeadlessBrowserContext becomes invalid after:
+ // a) Calling HeadlessBrowserContext::Close or
+ // b) Calling HeadlessBrowser::Shutdown
+ virtual HeadlessBrowserContext::Builder CreateBrowserContextBuilder() = 0;
- // Deprecated. Use CreateWebContentsBuilder() instead.
- virtual HeadlessWebContents* CreateWebContents(const GURL& initial_url,
- const gfx::Size& size) = 0;
+ virtual std::vector<HeadlessBrowserContext*> GetAllBrowserContexts() = 0;
- virtual std::vector<HeadlessWebContents*> GetAllWebContents() = 0;
+ // Returns the HeadlessWebContents associated with the
+ // |devtools_agent_host_id| if any. Otherwise returns null.
+ virtual HeadlessWebContents* GetWebContentsForDevToolsAgentHostId(
+ const std::string& devtools_agent_host_id) = 0;
- // Returns a task runner for submitting work to the browser main thread.
- virtual scoped_refptr<base::SingleThreadTaskRunner> BrowserMainThread()
- const = 0;
+ // Returns HeadlessBrowserContext associated with the given id if any.
+ // Otherwise returns null.
+ virtual HeadlessBrowserContext* GetBrowserContextForId(
+ const std::string& id) = 0;
// Returns a task runner for submitting work to the browser file thread.
virtual scoped_refptr<base::SingleThreadTaskRunner> BrowserFileThread()
const = 0;
+ // Returns a task runner for submitting work to the browser io thread.
+ virtual scoped_refptr<base::SingleThreadTaskRunner> BrowserIOThread()
+ const = 0;
+
+ // Returns a task runner for submitting work to the browser main thread.
+ virtual scoped_refptr<base::SingleThreadTaskRunner> BrowserMainThread()
+ const = 0;
+
// Requests browser to stop as soon as possible. |Run| will return as soon as
// browser stops.
+ // IMPORTANT: All pointers to HeadlessBrowserContexts and HeadlessWebContents
+ // become invalid after calling this function.
virtual void Shutdown() = 0;
- // Create a new browser context, which can be used to isolate
- // HeadlessWebContents from one another.
- virtual HeadlessBrowserContext::Builder CreateBrowserContextBuilder() = 0;
-
protected:
HeadlessBrowser() {}
virtual ~HeadlessBrowser() {}
@@ -85,33 +94,52 @@ struct HeadlessBrowser::Options {
int argc;
const char** argv;
- std::string user_agent;
- std::string navigator_platform;
-
// Address at which DevTools should listen for connections. Disabled by
// default.
net::IPEndPoint devtools_endpoint;
+ // Optional message pump that overrides the default. Must outlive the browser.
+ base::MessagePump* message_pump;
+
+ // Run the browser in single process mode instead of using separate renderer
+ // processes as per default. Note that this also disables any sandboxing of
+ // web content, which can be a security risk.
+ bool single_process_mode;
+
+ // Run the browser without renderer sandbox. This option can be
+ // a security risk and should be used with caution.
+ bool disable_sandbox;
+
+ // Choose the GL implementation to use for rendering. A suitable
+ // implementantion is selected by default. Setting this to an empty
+ // string can be used to disable GL rendering (e.g., WebGL support).
+ std::string gl_implementation;
+
+ // Default per-context options, can be specialized on per-context basis.
+
+ std::string user_agent;
+
// Address of the HTTP/HTTPS proxy server to use. The system proxy settings
// are used by default.
net::HostPortPair proxy_server;
- // Optional message pump that overrides the default. Must outlive the browser.
- base::MessagePump* message_pump;
-
// Comma-separated list of rules that control how hostnames are mapped. See
// chrome::switches::kHostRules for a description for the format.
std::string host_resolver_rules;
- // Run the browser in single process mode instead of using separate renderer
- // processes as per default. Note that this also disables any sandboxing of
- // web content, which can be a security risk.
- bool single_process_mode;
+ // Default window size. This is also used to create the window tree host and
+ // as initial screen size. Defaults to 800x600.
+ gfx::Size window_size;
- // Custom network protocol handlers. These can be used to override URL
- // fetching for different network schemes.
- ProtocolHandlerMap protocol_handlers;
+ // Path to user data directory, where browser will look for its state.
+ // If empty, default directory (where the binary is located) will be used.
+ base::FilePath user_data_dir;
+ // Run a browser context in an incognito mode. Enabled by default.
+ bool incognito_mode;
+
+ // Reminder: when adding a new field here, do not forget to add it to
+ // HeadlessBrowserContextOptions (where appropriate).
private:
Options(int argc, const char** argv);
@@ -124,13 +152,22 @@ class HeadlessBrowser::Options::Builder {
Builder();
~Builder();
- Builder& SetUserAgent(const std::string& user_agent);
+ // Browser-wide settings.
+
Builder& EnableDevToolsServer(const net::IPEndPoint& endpoint);
Builder& SetMessagePump(base::MessagePump* message_pump);
+ Builder& SetSingleProcessMode(bool single_process_mode);
+ Builder& SetDisableSandbox(bool disable_sandbox);
+ Builder& SetGLImplementation(const std::string& gl_implementation);
+
+ // Per-context settings.
+
+ Builder& SetUserAgent(const std::string& user_agent);
Builder& SetProxyServer(const net::HostPortPair& proxy_server);
Builder& SetHostResolverRules(const std::string& host_resolver_rules);
- Builder& SetSingleProcessMode(bool single_process_mode);
- Builder& SetProtocolHandlers(ProtocolHandlerMap protocol_handlers);
+ Builder& SetWindowSize(const gfx::Size& window_size);
+ Builder& SetUserDataDir(const base::FilePath& user_data_dir);
+ Builder& SetIncognitoMode(bool incognito_mode);
Options Build();
diff --git a/chromium/headless/public/headless_browser_context.h b/chromium/headless/public/headless_browser_context.h
index f8c581c2fd5..b779f6a38da 100644
--- a/chromium/headless/public/headless_browser_context.h
+++ b/chromium/headless/public/headless_browser_context.h
@@ -5,11 +5,31 @@
#ifndef HEADLESS_PUBLIC_HEADLESS_BROWSER_CONTEXT_H_
#define HEADLESS_PUBLIC_HEADLESS_BROWSER_CONTEXT_H_
+#include <list>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/optional.h"
+#include "content/public/common/web_preferences.h"
#include "headless/public/headless_export.h"
+#include "headless/public/headless_web_contents.h"
+#include "net/base/host_port_pair.h"
#include "net/url_request/url_request_job_factory.h"
+namespace base {
+class FilePath;
+}
+
namespace headless {
class HeadlessBrowserImpl;
+class HeadlessBrowserContextOptions;
+
+// Imported into headless namespace for
+// Builder::SetOverrideWebPreferencesCallback().
+using content::WebPreferences;
using ProtocolHandlerMap = std::unordered_map<
std::string,
@@ -17,12 +37,34 @@ using ProtocolHandlerMap = std::unordered_map<
// Represents an isolated session with a unique cache, cookies, and other
// profile/session related data.
+// When browser context is deleted, all associated web contents are closed.
class HEADLESS_EXPORT HeadlessBrowserContext {
public:
class Builder;
virtual ~HeadlessBrowserContext() {}
+ // Open a new tab. Returns a builder object which can be used to set
+ // properties for the new tab.
+ // Pointer to HeadlessWebContents becomes invalid after:
+ // a) Calling HeadlessWebContents::Close, or
+ // b) Calling HeadlessBrowserContext::Close on associated browser context, or
+ // c) Calling HeadlessBrowser::Shutdown.
+ virtual HeadlessWebContents::Builder CreateWebContentsBuilder() = 0;
+
+ // Returns all web contents owned by this browser context.
+ virtual std::vector<HeadlessWebContents*> GetAllWebContents() = 0;
+
+ // See HeadlessBrowser::GetWebContentsForDevToolsAgentHostId.
+ virtual HeadlessWebContents* GetWebContentsForDevToolsAgentHostId(
+ const std::string& devtools_agent_host_id) = 0;
+
+ // Destroy this BrowserContext and all WebContents associated with it.
+ virtual void Close() = 0;
+
+ // GUID for this browser context.
+ virtual const std::string& Id() const = 0;
+
// TODO(skyostil): Allow saving and restoring contexts (crbug.com/617931).
protected:
@@ -41,15 +83,70 @@ class HEADLESS_EXPORT HeadlessBrowserContext::Builder {
// fetching for different network schemes.
Builder& SetProtocolHandlers(ProtocolHandlerMap protocol_handlers);
- std::unique_ptr<HeadlessBrowserContext> Build();
+ // Specify JS mojo module bindings to be installed, one per mojom file.
+ // Note a single mojom file could potentially define many interfaces.
+ // |mojom_name| the name including path of the .mojom file.
+ // |js_bindings| compiletime generated javascript bindings. Typically loaded
+ // from gen/path/name.mojom.js.
+ Builder& AddJsMojoBindings(const std::string& mojom_name,
+ const std::string& js_bindings);
+
+ // By default if you add mojo bindings, http and https are disabled because
+ // its almost certinly unsafe for arbitary sites on the internet to have
+ // access to these bindings. If you know what you're doing it may be OK to
+ // turn them back on. E.g. if headless_lib is being used in a testing
+ // framework which serves the web content from disk that's likely ok.
+ //
+ // That said, best pratice is to add a ProtocolHandler to serve the
+ // webcontent over a custom protocol. That way you can be sure that only the
+ // things you intend have access to mojo.
+ 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& 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);
+
+ HeadlessBrowserContext* Build();
private:
friend class HeadlessBrowserImpl;
+ friend class HeadlessBrowserContextImpl;
explicit Builder(HeadlessBrowserImpl* browser);
+ struct MojoBindings {
+ MojoBindings();
+ MojoBindings(const std::string& mojom_name, const std::string& js_bindings);
+ ~MojoBindings();
+
+ std::string mojom_name;
+ std::string js_bindings;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MojoBindings);
+ };
+
HeadlessBrowserImpl* browser_;
- ProtocolHandlerMap protocol_handlers_;
+ std::unique_ptr<HeadlessBrowserContextOptions> options_;
+
+ std::list<MojoBindings> mojo_bindings_;
+ bool enable_http_and_https_if_mojo_used_;
DISALLOW_COPY_AND_ASSIGN(Builder);
};
diff --git a/chromium/headless/public/headless_devtools_client.h b/chromium/headless/public/headless_devtools_client.h
index a71ce657200..36e6ea3877a 100644
--- a/chromium/headless/public/headless_devtools_client.h
+++ b/chromium/headless/public/headless_devtools_client.h
@@ -21,6 +21,9 @@ class Domain;
namespace application_cache {
class Domain;
}
+namespace browser {
+class Domain;
+}
namespace cache_storage {
class Domain;
}
@@ -69,6 +72,9 @@ class Domain;
namespace layer_tree {
class Domain;
}
+namespace log {
+class Domain;
+}
namespace memory {
class Domain;
}
@@ -115,6 +121,7 @@ class HEADLESS_EXPORT HeadlessDevToolsClient {
virtual accessibility::Domain* GetAccessibility() = 0;
virtual animation::Domain* GetAnimation() = 0;
virtual application_cache::Domain* GetApplicationCache() = 0;
+ virtual browser::Domain* GetBrowser() = 0;
virtual cache_storage::Domain* GetCacheStorage() = 0;
virtual console::Domain* GetConsole() = 0;
virtual css::Domain* GetCSS() = 0;
@@ -131,6 +138,7 @@ class HEADLESS_EXPORT HeadlessDevToolsClient {
virtual inspector::Domain* GetInspector() = 0;
virtual io::Domain* GetIO() = 0;
virtual layer_tree::Domain* GetLayerTree() = 0;
+ virtual log::Domain* GetLog() = 0;
virtual memory::Domain* GetMemory() = 0;
virtual network::Domain* GetNetwork() = 0;
virtual page::Domain* GetPage() = 0;
diff --git a/chromium/headless/public/headless_web_contents.h b/chromium/headless/public/headless_web_contents.h
index a193d5042e6..0ca6a588bb5 100644
--- a/chromium/headless/public/headless_web_contents.h
+++ b/chromium/headless/public/headless_web_contents.h
@@ -5,14 +5,19 @@
#ifndef HEADLESS_PUBLIC_HEADLESS_WEB_CONTENTS_H_
#define HEADLESS_PUBLIC_HEADLESS_WEB_CONTENTS_H_
+#include <list>
+#include <string>
+#include <utility>
+
#include "base/callback.h"
#include "base/macros.h"
#include "headless/public/headless_export.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
#include "ui/gfx/geometry/size.h"
#include "url/gurl.h"
namespace headless {
-class HeadlessBrowserContext;
+class HeadlessBrowserContextImpl;
class HeadlessBrowserImpl;
class HeadlessDevToolsTarget;
@@ -73,14 +78,24 @@ class HEADLESS_EXPORT HeadlessWebContents::Builder {
// about:blank.
Builder& SetInitialURL(const GURL& initial_url);
- // Specify the initial window size (default is 800x600).
+ // Specify the initial window size (default is configured in browser options).
Builder& SetWindowSize(const gfx::Size& size);
- // Set a browser context for storing session data (e.g., cookies, cache, local
- // storage) for the tab. Several tabs can share the same browser context. If
- // unset, the default browser context will be used. The browser context must
- // outlive this HeadlessWebContents.
- Builder& SetBrowserContext(HeadlessBrowserContext* browser_context);
+ // Specify an embedder provided Mojo service to be installed. The
+ // |service_factory| callback is called on demand by Mojo to instantiate the
+ // service if a client asks for it.
+ template <typename Interface>
+ Builder& AddMojoService(
+ const base::Callback<void(mojo::InterfaceRequest<Interface>)>&
+ service_factory) {
+ return AddMojoService(
+ Interface::Name_,
+ base::Bind(&Builder::ForwardToServiceFactory<Interface>,
+ service_factory));
+ }
+ Builder& AddMojoService(const std::string& service_name,
+ const base::Callback<void(
+ mojo::ScopedMessagePipeHandle)>& service_factory);
// The returned object is owned by HeadlessBrowser. Call
// HeadlessWebContents::Close() to dispose it.
@@ -88,14 +103,38 @@ class HEADLESS_EXPORT HeadlessWebContents::Builder {
private:
friend class HeadlessBrowserImpl;
+ friend class HeadlessBrowserContextImpl;
friend class HeadlessWebContentsImpl;
- explicit Builder(HeadlessBrowserImpl* browser);
+ explicit Builder(HeadlessBrowserContextImpl* browser_context);
+
+ template <typename Interface>
+ static void ForwardToServiceFactory(
+ const base::Callback<void(mojo::InterfaceRequest<Interface>)>&
+ service_factory,
+ mojo::ScopedMessagePipeHandle handle) {
+ service_factory.Run(mojo::MakeRequest<Interface>(std::move(handle)));
+ }
+
+ struct MojoService {
+ MojoService();
+ MojoService(const std::string& service_name,
+ const base::Callback<void(mojo::ScopedMessagePipeHandle)>&
+ service_factory);
+ ~MojoService();
+
+ std::string service_name;
+ base::Callback<void(mojo::ScopedMessagePipeHandle)> service_factory;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MojoService);
+ };
+
+ HeadlessBrowserContextImpl* browser_context_;
- HeadlessBrowserImpl* browser_;
GURL initial_url_ = GURL("about:blank");
- gfx::Size window_size_ = gfx::Size(800, 600);
- HeadlessBrowserContext* browser_context_;
+ gfx::Size window_size_;
+ std::list<MojoService> mojo_services_;
DISALLOW_COPY_AND_ASSIGN(Builder);
};
diff --git a/chromium/headless/public/internal/value_conversions.h b/chromium/headless/public/internal/value_conversions.h
index 17c6eda3f22..c25a0cd1ce2 100644
--- a/chromium/headless/public/internal/value_conversions.h
+++ b/chromium/headless/public/internal/value_conversions.h
@@ -32,22 +32,22 @@ struct FromValue {
// partially specialize vector types.
template <typename T>
std::unique_ptr<base::Value> ToValueImpl(int value, T*) {
- return base::WrapUnique(new base::FundamentalValue(value));
+ return base::MakeUnique<base::FundamentalValue>(value);
}
template <typename T>
std::unique_ptr<base::Value> ToValueImpl(double value, T*) {
- return base::WrapUnique(new base::FundamentalValue(value));
+ return base::MakeUnique<base::FundamentalValue>(value);
}
template <typename T>
std::unique_ptr<base::Value> ToValueImpl(bool value, T*) {
- return base::WrapUnique(new base::FundamentalValue(value));
+ return base::MakeUnique<base::FundamentalValue>(value);
}
template <typename T>
std::unique_ptr<base::Value> ToValueImpl(const std::string& value, T*) {
- return base::WrapUnique(new base::StringValue(value));
+ return base::MakeUnique<base::StringValue>(value);
}
template <typename T>
diff --git a/chromium/headless/public/util/black_hole_protocol_handler.cc b/chromium/headless/public/util/black_hole_protocol_handler.cc
new file mode 100644
index 00000000000..3f036c59706
--- /dev/null
+++ b/chromium/headless/public/util/black_hole_protocol_handler.cc
@@ -0,0 +1,66 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/black_hole_protocol_handler.h"
+
+#include "base/threading/thread_task_runner_handle.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request_job.h"
+
+namespace headless {
+namespace {
+class BlackHoleRequestJob : public net::URLRequestJob {
+ public:
+ BlackHoleRequestJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate);
+
+ // net::URLRequestJob implementation:
+ void Start() override;
+ void Kill() override;
+
+ private:
+ ~BlackHoleRequestJob() override;
+
+ void StartAsync();
+
+ base::WeakPtrFactory<BlackHoleRequestJob> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlackHoleRequestJob);
+};
+
+BlackHoleRequestJob::BlackHoleRequestJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate)
+ : net::URLRequestJob(request, network_delegate), weak_factory_(this) {}
+
+BlackHoleRequestJob::~BlackHoleRequestJob() {}
+
+void BlackHoleRequestJob::Start() {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&BlackHoleRequestJob::StartAsync, weak_factory_.GetWeakPtr()));
+}
+
+void BlackHoleRequestJob::StartAsync() {
+ // Fail every request!
+ NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
+ net::ERR_FILE_NOT_FOUND));
+}
+
+void BlackHoleRequestJob::Kill() {
+ weak_factory_.InvalidateWeakPtrs();
+ URLRequestJob::Kill();
+}
+} // namespace
+
+BlackHoleProtocolHandler::BlackHoleProtocolHandler() {}
+BlackHoleProtocolHandler::~BlackHoleProtocolHandler() {}
+
+net::URLRequestJob* BlackHoleProtocolHandler::MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const {
+ return new BlackHoleRequestJob(request, network_delegate);
+}
+
+} // namespace headless
diff --git a/chromium/headless/public/util/black_hole_protocol_handler.h b/chromium/headless/public/util/black_hole_protocol_handler.h
new file mode 100644
index 00000000000..7d728904837
--- /dev/null
+++ b/chromium/headless/public/util/black_hole_protocol_handler.h
@@ -0,0 +1,31 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_PUBLIC_UTIL_BLACK_HOLE_PROTOCOL_HANDLER_H_
+#define HEADLESS_PUBLIC_UTIL_BLACK_HOLE_PROTOCOL_HANDLER_H_
+
+#include <map>
+
+#include "net/url_request/url_request_job_factory.h"
+
+namespace headless {
+
+// A protocol handler that fails any request sent to it.
+class BlackHoleProtocolHandler
+ : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ BlackHoleProtocolHandler();
+ ~BlackHoleProtocolHandler() override;
+
+ net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BlackHoleProtocolHandler);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_BLACK_HOLE_PROTOCOL_HANDLER_H_
diff --git a/chromium/headless/public/util/deterministic_dispatcher.cc b/chromium/headless/public/util/deterministic_dispatcher.cc
new file mode 100644
index 00000000000..70696eaeaa9
--- /dev/null
+++ b/chromium/headless/public/util/deterministic_dispatcher.cc
@@ -0,0 +1,95 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/deterministic_dispatcher.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "headless/public/util/managed_dispatch_url_request_job.h"
+
+namespace headless {
+
+DeterministicDispatcher::DeterministicDispatcher(
+ scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner)
+ : io_thread_task_runner_(std::move(io_thread_task_runner)),
+ dispatch_pending_(false) {}
+
+DeterministicDispatcher::~DeterministicDispatcher() {}
+
+void DeterministicDispatcher::JobCreated(ManagedDispatchURLRequestJob* job) {
+ base::AutoLock lock(lock_);
+ pending_requests_.push_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) {
+ pending_requests_.erase(it);
+ break;
+ }
+ }
+ ready_status_map_.erase(job);
+ // We rely on JobDeleted getting called to call MaybeDispatchJobLocked.
+}
+
+void DeterministicDispatcher::JobFailed(ManagedDispatchURLRequestJob* job,
+ net::Error error) {
+ base::AutoLock lock(lock_);
+ ready_status_map_[job] = error;
+ MaybeDispatchJobLocked();
+}
+
+void DeterministicDispatcher::DataReady(ManagedDispatchURLRequestJob* job) {
+ base::AutoLock lock(lock_);
+ ready_status_map_[job] = net::OK;
+ MaybeDispatchJobLocked();
+}
+
+void DeterministicDispatcher::JobDeleted(ManagedDispatchURLRequestJob* job) {
+ base::AutoLock lock(lock_);
+ MaybeDispatchJobLocked();
+}
+
+void DeterministicDispatcher::MaybeDispatchJobLocked() {
+ if (dispatch_pending_ || ready_status_map_.empty())
+ return;
+
+ dispatch_pending_ = true;
+ io_thread_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&DeterministicDispatcher::MaybeDispatchJobOnIOThreadTask,
+ base::Unretained(this)));
+}
+
+void DeterministicDispatcher::MaybeDispatchJobOnIOThreadTask() {
+ ManagedDispatchURLRequestJob* job;
+ net::Error job_status;
+
+ {
+ base::AutoLock lock(lock_);
+ CHECK(!pending_requests_.empty());
+ dispatch_pending_ = false;
+ 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())
+ return;
+
+ job_status = it->second;
+ ready_status_map_.erase(it);
+ pending_requests_.pop_front();
+ }
+
+ if (job_status == net::OK) {
+ job->OnHeadersComplete();
+ } else {
+ job->OnStartError(job_status);
+ }
+}
+
+} // namespace headless
diff --git a/chromium/headless/public/util/deterministic_dispatcher.h b/chromium/headless/public/util/deterministic_dispatcher.h
new file mode 100644
index 00000000000..c2e73b61710
--- /dev/null
+++ b/chromium/headless/public/util/deterministic_dispatcher.h
@@ -0,0 +1,62 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_PUBLIC_UTIL_DETERMINISTIC_DISPATCHER_H_
+#define HEADLESS_PUBLIC_UTIL_DETERMINISTIC_DISPATCHER_H_
+
+#include <deque>
+#include <map>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "headless/public/util/url_request_dispatcher.h"
+#include "net/base/net_errors.h"
+
+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
+// helps make renders deterministic at the cost of slower page loads.
+class DeterministicDispatcher : public URLRequestDispatcher {
+ public:
+ explicit DeterministicDispatcher(
+ scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner);
+
+ ~DeterministicDispatcher() override;
+
+ // UrlRequestDispatcher implementation:
+ void JobCreated(ManagedDispatchURLRequestJob* job) override;
+ void JobKilled(ManagedDispatchURLRequestJob* job) override;
+ void JobFailed(ManagedDispatchURLRequestJob* job, net::Error error) override;
+ void DataReady(ManagedDispatchURLRequestJob* job) override;
+ void JobDeleted(ManagedDispatchURLRequestJob* job) override;
+
+ private:
+ void MaybeDispatchJobLocked();
+ void MaybeDispatchJobOnIOThreadTask();
+
+ scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner_;
+
+ // Protects all members below.
+ base::Lock lock_;
+
+ std::deque<ManagedDispatchURLRequestJob*> pending_requests_;
+
+ using StatusMap = std::map<ManagedDispatchURLRequestJob*, net::Error>;
+ StatusMap ready_status_map_;
+
+ // Whether or not a MaybeDispatchJobOnIoThreadTask has been posted on the
+ // |io_thread_task_runner_|
+ bool dispatch_pending_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeterministicDispatcher);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_DETERMINISTIC_DISPATCHER_H_
diff --git a/chromium/headless/public/util/deterministic_dispatcher_test.cc b/chromium/headless/public/util/deterministic_dispatcher_test.cc
new file mode 100644
index 00000000000..b4c1c1fe089
--- /dev/null
+++ b/chromium/headless/public/util/deterministic_dispatcher_test.cc
@@ -0,0 +1,113 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/deterministic_dispatcher.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.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"
+
+using testing::ElementsAre;
+
+namespace headless {
+
+class DeterministicDispatcherTest : public ::testing::Test {
+ protected:
+ DeterministicDispatcherTest() {}
+ ~DeterministicDispatcherTest() override {}
+
+ void SetUp() override {
+ deterministic_dispatcher_.reset(
+ new DeterministicDispatcher(loop_.task_runner()));
+ }
+
+ base::MessageLoop loop_;
+ std::unique_ptr<DeterministicDispatcher> deterministic_dispatcher_;
+};
+
+TEST_F(DeterministicDispatcherTest, DispatchDataReadyInReverseOrder) {
+ std::vector<std::string> notifications;
+ std::unique_ptr<FakeManagedDispatchURLRequestJob> job1(
+ new FakeManagedDispatchURLRequestJob(deterministic_dispatcher_.get(), 1,
+ &notifications));
+ 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));
+ job4->DispatchHeadersComplete();
+ job3->DispatchHeadersComplete();
+ job2->DispatchHeadersComplete();
+ job1->DispatchHeadersComplete();
+
+ EXPECT_TRUE(notifications.empty());
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_THAT(notifications, ElementsAre("id: 1 OK"));
+
+ job1.reset();
+ 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"));
+}
+
+TEST_F(DeterministicDispatcherTest,
+ ErrorsAndDataReadyDispatchedInCreationOrder) {
+ std::vector<std::string> notifications;
+ std::unique_ptr<FakeManagedDispatchURLRequestJob> job1(
+ new FakeManagedDispatchURLRequestJob(deterministic_dispatcher_.get(), 1,
+ &notifications));
+ 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));
+ job4->DispatchHeadersComplete();
+ job3->DispatchStartError(static_cast<net::Error>(-123));
+ job2->DispatchHeadersComplete();
+ job1->DispatchHeadersComplete();
+
+ EXPECT_TRUE(notifications.empty());
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_THAT(notifications, ElementsAre("id: 1 OK"));
+
+ job1.reset();
+ 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 err: -123"));
+
+ job3.reset();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_THAT(notifications, ElementsAre("id: 1 OK", "id: 2 OK",
+ "id: 3 err: -123", "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
new file mode 100644
index 00000000000..bb2c1117e21
--- /dev/null
+++ b/chromium/headless/public/util/deterministic_http_protocol_handler.cc
@@ -0,0 +1,80 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/deterministic_http_protocol_handler.h"
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "headless/public/headless_browser_context.h"
+#include "headless/public/util/deterministic_dispatcher.h"
+#include "headless/public/util/generic_url_request_job.h"
+#include "headless/public/util/http_url_fetcher.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+
+namespace headless {
+
+class DeterministicHttpProtocolHandler::NopGenericURLRequestJobDelegate
+ : public GenericURLRequestJob::Delegate {
+ public:
+ NopGenericURLRequestJobDelegate() {}
+ ~NopGenericURLRequestJobDelegate() override {}
+
+ // GenericURLRequestJob::Delegate methods:
+ bool BlockOrRewriteRequest(
+ const GURL& url,
+ const std::string& method,
+ const std::string& referrer,
+ GenericURLRequestJob::RewriteCallback callback) override {
+ return false;
+ }
+
+ const GenericURLRequestJob::HttpResponse* MaybeMatchResource(
+ const GURL& url,
+ const std::string& method,
+ const net::HttpRequestHeaders& request_headers) override {
+ return nullptr;
+ }
+
+ void OnResourceLoadComplete(const GURL& final_url,
+ const std::string& mime_type,
+ int http_response_code) override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NopGenericURLRequestJobDelegate);
+};
+
+DeterministicHttpProtocolHandler::DeterministicHttpProtocolHandler(
+ DeterministicDispatcher* deterministic_dispatcher,
+ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
+ : deterministic_dispatcher_(deterministic_dispatcher),
+ io_task_runner_(io_task_runner),
+ nop_delegate_(new NopGenericURLRequestJobDelegate()) {}
+
+DeterministicHttpProtocolHandler::~DeterministicHttpProtocolHandler() {
+ if (url_request_context_)
+ io_task_runner_->DeleteSoon(FROM_HERE, url_request_context_.release());
+ if (url_request_job_factory_)
+ io_task_runner_->DeleteSoon(FROM_HERE, url_request_job_factory_.release());
+}
+
+net::URLRequestJob* DeterministicHttpProtocolHandler::MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const {
+ if (!url_request_context_) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ // Create our own URLRequestContext with an empty URLRequestJobFactoryImpl
+ // which lets us use the default http(s) RequestJobs.
+ url_request_context_.reset(new net::URLRequestContext());
+ url_request_context_->CopyFrom(request->context());
+ url_request_job_factory_.reset(new net::URLRequestJobFactoryImpl());
+ url_request_context_->set_job_factory(url_request_job_factory_.get());
+ }
+ return new GenericURLRequestJob(
+ request, network_delegate, deterministic_dispatcher_,
+ base::MakeUnique<HttpURLFetcher>(url_request_context_.get()),
+ nop_delegate_.get());
+}
+
+} // namespace headless
diff --git a/chromium/headless/public/util/deterministic_http_protocol_handler.h b/chromium/headless/public/util/deterministic_http_protocol_handler.h
new file mode 100644
index 00000000000..da0a70d1e4b
--- /dev/null
+++ b/chromium/headless/public/util/deterministic_http_protocol_handler.h
@@ -0,0 +1,59 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_PUBLIC_UTIL_DETERMINISTIC_HTTP_PROTOCOL_HANDLER_H_
+#define HEADLESS_PUBLIC_UTIL_DETERMINISTIC_HTTP_PROTOCOL_HANDLER_H_
+
+#include <memory>
+
+#include "base/single_thread_task_runner.h"
+#include "net/url_request/url_request_job_factory.h"
+
+namespace net {
+class URLRequestContext;
+class URLRequestJobFactory;
+} // namespace
+
+namespace headless {
+class DeterministicDispatcher;
+class HeadlessBrowserContext;
+
+// A deterministic protocol handler. Requests made to this protocol handler
+// will return in order of creation, regardless of what order the network
+// returns them in. This helps remove one large source of network related
+// non determinism at the cost of slower page loads.
+class DeterministicHttpProtocolHandler
+ : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ // Note |deterministic_dispatcher| is expected to be shared across a number of
+ // protocol handlers, e.g. for http & https protocols.
+ DeterministicHttpProtocolHandler(
+ DeterministicDispatcher* deterministic_dispatcher,
+ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner);
+ ~DeterministicHttpProtocolHandler() override;
+
+ net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override;
+
+ private:
+ class NopGenericURLRequestJobDelegate;
+
+ DeterministicDispatcher* deterministic_dispatcher_; // NOT OWNED.
+ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+ std::unique_ptr<NopGenericURLRequestJobDelegate> nop_delegate_;
+
+ // |url_request_context_| and |url_request_job_factory_| are lazily created on
+ // the IO thread. The URLRequestContext is setup to bypass any user-specified
+ // protocol handlers including this one. This is necessary to actually fetch
+ // http resources.
+ mutable std::unique_ptr<net::URLRequestContext> url_request_context_;
+ mutable std::unique_ptr<net::URLRequestJobFactory> url_request_job_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeterministicHttpProtocolHandler);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_DETERMINISTIC_HTTP_PROTOCOL_HANDLER_H_
diff --git a/chromium/headless/public/util/error_reporter.cc b/chromium/headless/public/util/error_reporter.cc
index 54c0758b71d..88722d7b79d 100644
--- a/chromium/headless/public/util/error_reporter.cc
+++ b/chromium/headless/public/util/error_reporter.cc
@@ -24,7 +24,7 @@ void ErrorReporter::Pop() {
void ErrorReporter::SetName(const char* name) {
DCHECK(!path_.empty());
- path_[path_.size() - 1] = name;
+ path_.back() = name;
}
void ErrorReporter::AddError(base::StringPiece description) {
diff --git a/chromium/headless/public/util/expedited_dispatcher.cc b/chromium/headless/public/util/expedited_dispatcher.cc
new file mode 100644
index 00000000000..0120442dd0e
--- /dev/null
+++ b/chromium/headless/public/util/expedited_dispatcher.cc
@@ -0,0 +1,39 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/expedited_dispatcher.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "headless/public/util/managed_dispatch_url_request_job.h"
+
+namespace headless {
+
+ExpeditedDispatcher::ExpeditedDispatcher(
+ scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner)
+ : io_thread_task_runner_(std::move(io_thread_task_runner)) {}
+
+ExpeditedDispatcher::~ExpeditedDispatcher() {}
+
+void ExpeditedDispatcher::JobCreated(ManagedDispatchURLRequestJob*) {}
+
+void ExpeditedDispatcher::JobKilled(ManagedDispatchURLRequestJob*) {}
+
+void ExpeditedDispatcher::JobFailed(ManagedDispatchURLRequestJob* job,
+ net::Error error) {
+ io_thread_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&ManagedDispatchURLRequestJob::OnStartError,
+ base::Unretained(job), error));
+}
+
+void ExpeditedDispatcher::DataReady(ManagedDispatchURLRequestJob* job) {
+ io_thread_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&ManagedDispatchURLRequestJob::OnHeadersComplete,
+ base::Unretained(job)));
+}
+
+void ExpeditedDispatcher::JobDeleted(ManagedDispatchURLRequestJob*) {}
+
+} // namespace headless
diff --git a/chromium/headless/public/util/expedited_dispatcher.h b/chromium/headless/public/util/expedited_dispatcher.h
new file mode 100644
index 00000000000..39b8057fdfc
--- /dev/null
+++ b/chromium/headless/public/util/expedited_dispatcher.h
@@ -0,0 +1,41 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_PUBLIC_UTIL_EXPEDITED_DISPATCHER_H_
+#define HEADLESS_PUBLIC_UTIL_EXPEDITED_DISPATCHER_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "headless/public/util/url_request_dispatcher.h"
+#include "net/base/net_errors.h"
+
+namespace headless {
+
+class ManagedDispatchURLRequestJob;
+
+// This class dispatches calls to OnHeadersComplete and OnStartError
+// immediately sacrificing determinism for performance.
+class ExpeditedDispatcher : public URLRequestDispatcher {
+ public:
+ explicit ExpeditedDispatcher(
+ scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner);
+ ~ExpeditedDispatcher() override;
+
+ // UrlRequestDispatcher implementation:
+ void JobCreated(ManagedDispatchURLRequestJob* job) override;
+ void JobKilled(ManagedDispatchURLRequestJob* job) override;
+ void JobFailed(ManagedDispatchURLRequestJob* job, net::Error error) override;
+ void DataReady(ManagedDispatchURLRequestJob* job) override;
+ void JobDeleted(ManagedDispatchURLRequestJob* job) override;
+
+ private:
+ scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExpeditedDispatcher);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_EXPEDITED_DISPATCHER_H_
diff --git a/chromium/headless/public/util/expedited_dispatcher_test.cc b/chromium/headless/public/util/expedited_dispatcher_test.cc
new file mode 100644
index 00000000000..5ef6301ba1d
--- /dev/null
+++ b/chromium/headless/public/util/expedited_dispatcher_test.cc
@@ -0,0 +1,86 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/expedited_dispatcher.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.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"
+
+using testing::ElementsAre;
+
+namespace headless {
+
+class ExpeditedDispatcherTest : public ::testing::Test {
+ protected:
+ ExpeditedDispatcherTest() {}
+ ~ExpeditedDispatcherTest() override {}
+
+ void SetUp() override {
+ expedited_dispatcher_.reset(new ExpeditedDispatcher(loop_.task_runner()));
+ }
+
+ base::MessageLoop loop_;
+ std::unique_ptr<ExpeditedDispatcher> expedited_dispatcher_;
+};
+
+TEST_F(ExpeditedDispatcherTest, DispatchDataReadyInReverseOrder) {
+ std::vector<std::string> notifications;
+ std::unique_ptr<FakeManagedDispatchURLRequestJob> job1(
+ new FakeManagedDispatchURLRequestJob(expedited_dispatcher_.get(), 1,
+ &notifications));
+ 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));
+ job4->DispatchHeadersComplete();
+ job3->DispatchHeadersComplete();
+ job2->DispatchHeadersComplete();
+ job1->DispatchHeadersComplete();
+
+ EXPECT_TRUE(notifications.empty());
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_THAT(notifications,
+ ElementsAre("id: 4 OK", "id: 3 OK", "id: 2 OK", "id: 1 OK"));
+}
+
+TEST_F(ExpeditedDispatcherTest, ErrorsAndDataReadyDispatchedInCallOrder) {
+ std::vector<std::string> notifications;
+ std::unique_ptr<FakeManagedDispatchURLRequestJob> job1(
+ new FakeManagedDispatchURLRequestJob(expedited_dispatcher_.get(), 1,
+ &notifications));
+ 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));
+ job4->DispatchHeadersComplete();
+ job3->DispatchStartError(static_cast<net::Error>(-123));
+ job2->DispatchHeadersComplete();
+ job1->DispatchHeadersComplete();
+
+ EXPECT_TRUE(notifications.empty());
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_THAT(notifications, ElementsAre("id: 4 OK", "id: 3 err: -123",
+ "id: 2 OK", "id: 1 OK"));
+}
+
+} // namespace headless
diff --git a/chromium/headless/public/util/generic_url_request_job.cc b/chromium/headless/public/util/generic_url_request_job.cc
new file mode 100644
index 00000000000..9c7245bc430
--- /dev/null
+++ b/chromium/headless/public/util/generic_url_request_job.cc
@@ -0,0 +1,189 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/generic_url_request_job.h"
+
+#include <string.h>
+#include <algorithm>
+
+#include "base/logging.h"
+#include "headless/public/util/url_request_dispatcher.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "net/cookies/cookie_store.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_request_context.h"
+
+namespace headless {
+namespace {
+
+// True if the request method is "safe" (per section 4.2.1 of RFC 7231).
+bool IsMethodSafe(const std::string& method) {
+ return method == "GET" || method == "HEAD" || method == "OPTIONS" ||
+ method == "TRACE";
+}
+
+} // namespace
+
+GenericURLRequestJob::GenericURLRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ URLRequestDispatcher* url_request_dispatcher,
+ std::unique_ptr<URLFetcher> url_fetcher,
+ Delegate* delegate)
+ : ManagedDispatchURLRequestJob(request,
+ network_delegate,
+ url_request_dispatcher),
+ url_fetcher_(std::move(url_fetcher)),
+ delegate_(delegate),
+ weak_factory_(this) {}
+
+GenericURLRequestJob::~GenericURLRequestJob() = default;
+
+void GenericURLRequestJob::SetExtraRequestHeaders(
+ const net::HttpRequestHeaders& headers) {
+ extra_request_headers_ = headers;
+}
+
+void GenericURLRequestJob::Start() {
+ auto callback = [this](RewriteResult result, const GURL& url,
+ const std::string& method) {
+ switch (result) {
+ case RewriteResult::kAllow:
+ // Note that we use the rewritten url for selecting cookies.
+ // Also, rewriting does not affect the request initiator.
+ PrepareCookies(url, method, url::Origin(url));
+ break;
+ case RewriteResult::kDeny:
+ DispatchStartError(net::ERR_FILE_NOT_FOUND);
+ break;
+ case RewriteResult::kFailure:
+ DispatchStartError(net::ERR_UNEXPECTED);
+ break;
+ default:
+ DCHECK(false);
+ }
+ };
+
+ if (!delegate_->BlockOrRewriteRequest(request_->url(), request_->method(),
+ request_->referrer(), callback)) {
+ PrepareCookies(request_->url(), request_->method(),
+ url::Origin(request_->first_party_for_cookies()));
+ }
+}
+
+void GenericURLRequestJob::PrepareCookies(const GURL& rewritten_url,
+ const std::string& method,
+ const url::Origin& site_for_cookies) {
+ net::CookieStore* cookie_store = request_->context()->cookie_store();
+ net::CookieOptions options;
+ options.set_include_httponly();
+
+ // See net::URLRequestHttpJob::AddCookieHeaderAndStart().
+ url::Origin requested_origin(rewritten_url);
+ if (net::registry_controlled_domains::SameDomainOrHost(
+ requested_origin, site_for_cookies,
+ net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
+ if (net::registry_controlled_domains::SameDomainOrHost(
+ requested_origin, request_->initiator(),
+ net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
+ options.set_same_site_cookie_mode(
+ net::CookieOptions::SameSiteCookieMode::INCLUDE_STRICT_AND_LAX);
+ } else if (IsMethodSafe(request_->method())) {
+ options.set_same_site_cookie_mode(
+ net::CookieOptions::SameSiteCookieMode::INCLUDE_LAX);
+ }
+ }
+
+ cookie_store->GetCookieListWithOptionsAsync(
+ rewritten_url, options,
+ base::Bind(&GenericURLRequestJob::OnCookiesAvailable,
+ weak_factory_.GetWeakPtr(), rewritten_url, method));
+}
+
+void GenericURLRequestJob::OnCookiesAvailable(
+ const GURL& rewritten_url,
+ const std::string& method,
+ const net::CookieList& cookie_list) {
+ // TODO(alexclarke): Set user agent.
+ // Pass cookies, the referrer and any extra headers into the fetch request.
+ extra_request_headers_.SetHeader(
+ net::HttpRequestHeaders::kCookie,
+ net::CookieStore::BuildCookieLine(cookie_list));
+
+ extra_request_headers_.SetHeader(net::HttpRequestHeaders::kReferer,
+ request_->referrer());
+
+ // The resource may have been supplied in the request.
+ const HttpResponse* matched_resource = delegate_->MaybeMatchResource(
+ rewritten_url, method, extra_request_headers_);
+
+ if (matched_resource) {
+ OnFetchCompleteExtractHeaders(
+ matched_resource->final_url, matched_resource->http_response_code,
+ matched_resource->response_data, matched_resource->response_data_size);
+ } else {
+ url_fetcher_->StartFetch(rewritten_url, method, extra_request_headers_,
+ this);
+ }
+}
+
+void GenericURLRequestJob::OnFetchStartError(net::Error error) {
+ DispatchStartError(error);
+}
+
+void GenericURLRequestJob::OnFetchComplete(
+ const GURL& final_url,
+ int http_response_code,
+ scoped_refptr<net::HttpResponseHeaders> response_headers,
+ const char* body,
+ size_t body_size) {
+ http_response_code_ = http_response_code;
+ response_headers_ = response_headers;
+ body_ = body;
+ body_size_ = body_size;
+
+ DispatchHeadersComplete();
+
+ std::string mime_type;
+ GetMimeType(&mime_type);
+
+ delegate_->OnResourceLoadComplete(final_url, mime_type, http_response_code);
+}
+
+int GenericURLRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size) {
+ // TODO(skyostil): Implement ranged fetches.
+ // TODO(alexclarke): Add coverage for all the cases below.
+ size_t bytes_available = body_size_ - read_offset_;
+ size_t bytes_to_copy =
+ std::min(static_cast<size_t>(buf_size), bytes_available);
+ if (bytes_to_copy) {
+ std::memcpy(buf->data(), &body_[read_offset_], bytes_to_copy);
+ read_offset_ += bytes_to_copy;
+ }
+ return bytes_to_copy;
+}
+
+int GenericURLRequestJob::GetResponseCode() const {
+ return http_response_code_;
+}
+
+void GenericURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
+ info->headers = response_headers_;
+}
+
+bool GenericURLRequestJob::GetMimeType(std::string* mime_type) const {
+ if (!response_headers_)
+ return false;
+ return response_headers_->GetMimeType(mime_type);
+}
+
+bool GenericURLRequestJob::GetCharset(std::string* charset) {
+ if (!response_headers_)
+ return false;
+ return response_headers_->GetCharset(charset);
+}
+
+} // namespace headless
diff --git a/chromium/headless/public/util/generic_url_request_job.h b/chromium/headless/public/util/generic_url_request_job.h
new file mode 100644
index 00000000000..514a5e176f9
--- /dev/null
+++ b/chromium/headless/public/util/generic_url_request_job.h
@@ -0,0 +1,132 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_PUBLIC_UTIL_GENERIC_URL_REQUEST_JOB_H_
+#define HEADLESS_PUBLIC_UTIL_GENERIC_URL_REQUEST_JOB_H_
+
+#include <stddef.h>
+#include <functional>
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "headless/public/util/managed_dispatch_url_request_job.h"
+#include "headless/public/util/url_fetcher.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request.h"
+#include "url/gurl.h"
+
+namespace net {
+class HttpResponseHeaders;
+class IOBuffer;
+} // namespace net
+
+namespace headless {
+
+class URLRequestDispatcher;
+
+// Intended for use in a protocol handler, this ManagedDispatchURLRequestJob has
+// the following features:
+//
+// 1. The delegate can extension observe / cancel and redirect requests
+// 2. The delegate can optionally provide the results, otherwise the specifed
+// fetcher is invoked.
+class GenericURLRequestJob : public ManagedDispatchURLRequestJob,
+ public URLFetcher::ResultListener {
+ public:
+ enum class RewriteResult { kAllow, kDeny, kFailure };
+ using RewriteCallback = std::function<
+ void(RewriteResult result, const GURL& url, const std::string& method)>;
+
+ struct HttpResponse {
+ GURL final_url;
+ int http_response_code;
+
+ // The HTTP headers and response body. Note the lifetime of |response_data|
+ // is expected to outlive the GenericURLRequestJob.
+ const char* response_data; // NOT OWNED
+ size_t response_data_size;
+ };
+
+ class Delegate {
+ public:
+ // Allows the delegate to rewrite the URL for a given request. Return true
+ // to signal that the rewrite is in progress and |callback| will be called
+ // 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& method,
+ const std::string& referrer,
+ RewriteCallback callback) = 0;
+
+ // 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& method,
+ const net::HttpRequestHeaders& request_headers) = 0;
+
+ // Signals that a resource load has finished. Called on an arbitrary thread.
+ virtual void OnResourceLoadComplete(const GURL& final_url,
+ const std::string& mime_type,
+ int http_response_code) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ // NOTE |url_request_dispatcher| and |delegate| must outlive the
+ // GenericURLRequestJob.
+ GenericURLRequestJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ URLRequestDispatcher* url_request_dispatcher,
+ std::unique_ptr<URLFetcher> url_fetcher,
+ Delegate* delegate);
+ ~GenericURLRequestJob() override;
+
+ // net::URLRequestJob implementation:
+ void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers) override;
+ void Start() override;
+ int ReadRawData(net::IOBuffer* buf, int buf_size) override;
+ int GetResponseCode() const override;
+ void GetResponseInfo(net::HttpResponseInfo* info) override;
+ bool GetMimeType(std::string* mime_type) const override;
+ bool GetCharset(std::string* charset) override;
+
+ // URLFetcher::FetchResultListener implementation:
+ void OnFetchStartError(net::Error error) override;
+ void OnFetchComplete(const GURL& final_url,
+ int http_response_code,
+ scoped_refptr<net::HttpResponseHeaders> response_headers,
+ const char* body,
+ size_t body_size) override;
+
+ private:
+ void PrepareCookies(const GURL& rewritten_url,
+ const std::string& method,
+ const url::Origin& site_for_cookies);
+
+ void OnCookiesAvailable(const GURL& rewritten_url,
+ const std::string& method,
+ const net::CookieList& cookie_list);
+
+ std::unique_ptr<URLFetcher> url_fetcher_;
+ net::HttpRequestHeaders extra_request_headers_;
+ scoped_refptr<net::HttpResponseHeaders> response_headers_;
+ Delegate* delegate_; // Not owned.
+ const char* body_ = nullptr; // Not owned.
+ int http_response_code_ = 0;
+ size_t body_size_ = 0;
+ size_t read_offset_ = 0;
+
+ base::WeakPtrFactory<GenericURLRequestJob> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(GenericURLRequestJob);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_GENERIC_URL_REQUEST_JOB_H_
diff --git a/chromium/headless/public/util/generic_url_request_job_test.cc b/chromium/headless/public/util/generic_url_request_job_test.cc
new file mode 100644
index 00000000000..989b5f61638
--- /dev/null
+++ b/chromium/headless/public/util/generic_url_request_job_test.cc
@@ -0,0 +1,366 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/generic_url_request_job.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "headless/public/util/expedited_dispatcher.h"
+#include "headless/public/util/testing/generic_url_request_mocks.h"
+#include "headless/public/util/url_fetcher.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+std::ostream& operator<<(std::ostream& os, const base::Value& value) {
+ std::string json;
+ base::JSONWriter::WriteWithOptions(
+ value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
+ os << json;
+ return os;
+}
+
+MATCHER_P(MatchesJson, json, json.c_str()) {
+ std::unique_ptr<base::Value> expected(
+ base::JSONReader::Read(json, base::JSON_PARSE_RFC));
+ return arg.Equals(expected.get());
+}
+
+namespace headless {
+
+namespace {
+
+class MockFetcher : public URLFetcher {
+ public:
+ MockFetcher(base::DictionaryValue* fetch_request,
+ const std::string& json_reply)
+ : fetch_reply_(base::JSONReader::Read(json_reply, base::JSON_PARSE_RFC)),
+ fetch_request_(fetch_request) {
+ CHECK(fetch_reply_) << "Invalid json: " << json_reply;
+ }
+
+ ~MockFetcher() override {}
+
+ void StartFetch(const GURL& url,
+ const std::string& method,
+ const net::HttpRequestHeaders& request_headers,
+ ResultListener* result_listener) override {
+ // Record the request.
+ fetch_request_->SetString("url", url.spec());
+ fetch_request_->SetString("method", method);
+ std::unique_ptr<base::DictionaryValue> headers(new base::DictionaryValue);
+ for (net::HttpRequestHeaders::Iterator it(request_headers); it.GetNext();) {
+ headers->SetString(it.name(), it.value());
+ }
+ fetch_request_->Set("headers", std::move(headers));
+
+ // Return the canned response.
+ base::DictionaryValue* reply_dictionary;
+ ASSERT_TRUE(fetch_reply_->GetAsDictionary(&reply_dictionary));
+ std::string final_url;
+ ASSERT_TRUE(reply_dictionary->GetString("url", &final_url));
+ int http_response_code;
+ ASSERT_TRUE(reply_dictionary->GetInteger("http_response_code",
+ &http_response_code));
+ ASSERT_TRUE(reply_dictionary->GetString("data", &response_data_));
+ base::DictionaryValue* reply_headers_dictionary;
+ ASSERT_TRUE(
+ reply_dictionary->GetDictionary("headers", &reply_headers_dictionary));
+ scoped_refptr<net::HttpResponseHeaders> response_headers(
+ new net::HttpResponseHeaders(""));
+ for (base::DictionaryValue::Iterator it(*reply_headers_dictionary);
+ !it.IsAtEnd(); it.Advance()) {
+ std::string value;
+ ASSERT_TRUE(it.value().GetAsString(&value));
+ response_headers->AddHeader(
+ base::StringPrintf("%s: %s", it.key().c_str(), value.c_str()));
+ }
+ result_listener->OnFetchComplete(
+ GURL(final_url), http_response_code, std::move(response_headers),
+ response_data_.c_str(), response_data_.size());
+ }
+
+ private:
+ std::unique_ptr<base::Value> fetch_reply_;
+ base::DictionaryValue* fetch_request_; // NOT OWNED
+ std::string response_data_; // Here to ensure the required lifetime.
+};
+
+class MockProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ // Details of the fetch will be stored in |fetch_request|.
+ // The fetch response will be created from parsing |json_fetch_reply_|.
+ MockProtocolHandler(base::DictionaryValue* fetch_request,
+ std::string* json_fetch_reply,
+ URLRequestDispatcher* dispatcher,
+ GenericURLRequestJob::Delegate* job_delegate)
+ : fetch_request_(fetch_request),
+ json_fetch_reply_(json_fetch_reply),
+ job_delegate_(job_delegate),
+ dispatcher_(dispatcher) {}
+
+ // net::URLRequestJobFactory::ProtocolHandler override.
+ net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override {
+ return new GenericURLRequestJob(
+ request, network_delegate, dispatcher_,
+ base::MakeUnique<MockFetcher>(fetch_request_, *json_fetch_reply_),
+ job_delegate_);
+ }
+
+ private:
+ base::DictionaryValue* fetch_request_; // NOT OWNED
+ std::string* json_fetch_reply_; // NOT OWNED
+ GenericURLRequestJob::Delegate* job_delegate_; // NOT OWNED
+ URLRequestDispatcher* dispatcher_; // NOT OWNED
+};
+
+} // namespace
+
+class GenericURLRequestJobTest : public testing::Test {
+ public:
+ GenericURLRequestJobTest() : dispatcher_(message_loop_.task_runner()) {
+ url_request_job_factory_.SetProtocolHandler(
+ "https", base::WrapUnique(new MockProtocolHandler(
+ &fetch_request_, &json_fetch_reply_, &dispatcher_,
+ &job_delegate_)));
+ url_request_context_.set_job_factory(&url_request_job_factory_);
+ url_request_context_.set_cookie_store(&cookie_store_);
+ }
+
+ std::unique_ptr<net::URLRequest> CreateAndCompleteJob(
+ const GURL& url,
+ const std::string& json_reply) {
+ json_fetch_reply_ = json_reply;
+
+ std::unique_ptr<net::URLRequest> request(url_request_context_.CreateRequest(
+ url, net::DEFAULT_PRIORITY, &request_delegate_));
+ request->Start();
+ base::RunLoop().RunUntilIdle();
+ return request;
+ }
+
+ protected:
+ base::MessageLoop message_loop_;
+ ExpeditedDispatcher dispatcher_;
+
+ net::URLRequestJobFactoryImpl url_request_job_factory_;
+ net::URLRequestContext url_request_context_;
+ MockCookieStore cookie_store_;
+
+ MockURLRequestDelegate request_delegate_;
+ base::DictionaryValue fetch_request_; // The request sent to MockFetcher.
+ std::string json_fetch_reply_; // The reply to be sent by MockFetcher.
+ MockGenericURLRequestJobDelegate job_delegate_;
+};
+
+TEST_F(GenericURLRequestJobTest, BasicRequestParams) {
+ // TODO(alexclarke): Lobby for raw string literals and use them here!
+ json_fetch_reply_ =
+ "{\"url\":\"https://example.com\","
+ " \"http_response_code\":200,"
+ " \"data\":\"Reply\","
+ " \"headers\":{\"Content-Type\":\"text/plain\"}}";
+
+ std::unique_ptr<net::URLRequest> request(url_request_context_.CreateRequest(
+ GURL("https://example.com"), net::DEFAULT_PRIORITY, &request_delegate_));
+ request->SetReferrer("https://referrer.example.com");
+ request->SetExtraRequestHeaderByName("Extra-Header", "Value", true);
+ request->SetExtraRequestHeaderByName("User-Agent", "TestBrowser", true);
+ request->SetExtraRequestHeaderByName("Accept", "text/plain", true);
+ request->Start();
+ base::RunLoop().RunUntilIdle();
+
+ std::string expected_request_json =
+ "{\"url\": \"https://example.com/\","
+ " \"method\": \"GET\","
+ " \"headers\": {"
+ " \"Accept\": \"text/plain\","
+ " \"Cookie\": \"\","
+ " \"Extra-Header\": \"Value\","
+ " \"Referer\": \"https://referrer.example.com/\","
+ " \"User-Agent\": \"TestBrowser\""
+ " }"
+ "}";
+
+ EXPECT_THAT(fetch_request_, MatchesJson(expected_request_json));
+}
+
+TEST_F(GenericURLRequestJobTest, BasicRequestProperties) {
+ std::string reply =
+ "{\"url\":\"https://example.com\","
+ " \"http_response_code\":200,"
+ " \"data\":\"Reply\","
+ " \"headers\":{\"Content-Type\":\"text/html; charset=UTF-8\"}}";
+
+ std::unique_ptr<net::URLRequest> request(
+ CreateAndCompleteJob(GURL("https://example.com"), reply));
+
+ EXPECT_EQ(200, request->GetResponseCode());
+
+ std::string mime_type;
+ request->GetMimeType(&mime_type);
+ EXPECT_EQ("text/html", mime_type);
+
+ std::string charset;
+ request->GetCharset(&charset);
+ EXPECT_EQ("utf-8", charset);
+
+ std::string content_type;
+ EXPECT_TRUE(request->response_info().headers->GetNormalizedHeader(
+ "Content-Type", &content_type));
+ EXPECT_EQ("text/html; charset=UTF-8", content_type);
+}
+
+TEST_F(GenericURLRequestJobTest, BasicRequestContents) {
+ std::string reply =
+ "{\"url\":\"https://example.com\","
+ " \"http_response_code\":200,"
+ " \"data\":\"Reply\","
+ " \"headers\":{\"Content-Type\":\"text/html; charset=UTF-8\"}}";
+
+ std::unique_ptr<net::URLRequest> request(
+ CreateAndCompleteJob(GURL("https://example.com"), reply));
+
+ const int kBufferSize = 256;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));
+ int bytes_read;
+ EXPECT_TRUE(request->Read(buffer.get(), kBufferSize, &bytes_read));
+ EXPECT_EQ(5, bytes_read);
+ EXPECT_EQ("Reply", std::string(buffer->data(), 5));
+}
+
+TEST_F(GenericURLRequestJobTest, ReadInParts) {
+ std::string reply =
+ "{\"url\":\"https://example.com\","
+ " \"http_response_code\":200,"
+ " \"data\":\"Reply\","
+ " \"headers\":{\"Content-Type\":\"text/html; charset=UTF-8\"}}";
+
+ std::unique_ptr<net::URLRequest> request(
+ CreateAndCompleteJob(GURL("https://example.com"), reply));
+
+ const int kBufferSize = 3;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));
+ int bytes_read;
+ EXPECT_TRUE(request->Read(buffer.get(), kBufferSize, &bytes_read));
+ EXPECT_EQ(3, bytes_read);
+ EXPECT_EQ("Rep", std::string(buffer->data(), bytes_read));
+
+ EXPECT_TRUE(request->Read(buffer.get(), kBufferSize, &bytes_read));
+ EXPECT_EQ(2, bytes_read);
+ EXPECT_EQ("ly", std::string(buffer->data(), bytes_read));
+
+ EXPECT_TRUE(request->Read(buffer.get(), kBufferSize, &bytes_read));
+ EXPECT_EQ(0, bytes_read);
+}
+
+TEST_F(GenericURLRequestJobTest, RequestWithCookies) {
+ net::CookieList* cookies = cookie_store_.cookies();
+
+ // Basic matching cookie.
+ cookies->push_back(*net::CanonicalCookie::Create(
+ GURL("https://example.com"), "basic_cookie", "1", "example.com", "/",
+ base::Time(), base::Time(),
+ /* secure */ false,
+ /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
+ /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
+
+ // Matching secure cookie.
+ cookies->push_back(*net::CanonicalCookie::Create(
+ GURL("https://example.com"), "secure_cookie", "2", "example.com", "/",
+ base::Time(), base::Time(),
+ /* secure */ true,
+ /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
+ /* enforce_strict_secure */ true, net::COOKIE_PRIORITY_DEFAULT));
+
+ // Matching http-only cookie.
+ cookies->push_back(*net::CanonicalCookie::Create(
+ GURL("https://example.com"), "http_only_cookie", "3", "example.com", "/",
+ base::Time(), base::Time(),
+ /* secure */ false,
+ /* http_only */ true, net::CookieSameSite::NO_RESTRICTION,
+ /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
+
+ // Matching cookie with path.
+ cookies->push_back(*net::CanonicalCookie::Create(
+ GURL("https://example.com"), "cookie_with_path", "4", "example.com",
+ "/widgets", base::Time(), base::Time(),
+ /* secure */ false,
+ /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
+ /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
+
+ // Matching cookie with subdomain.
+ cookies->push_back(*net::CanonicalCookie::Create(
+ GURL("https://cdn.example.com"), "bad_subdomain_cookie", "5",
+ "cdn.example.com", "/", base::Time(), base::Time(),
+ /* secure */ false,
+ /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
+ /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
+
+ // Non-matching cookie (different site).
+ cookies->push_back(*net::CanonicalCookie::Create(
+ GURL("https://zombo.com"), "bad_site_cookie", "6", "zombo.com", "/",
+ base::Time(), base::Time(),
+ /* secure */ false,
+ /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
+ /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
+
+ // Non-matching cookie (different path).
+ cookies->push_back(*net::CanonicalCookie::Create(
+ GURL("https://example.com"), "bad_path_cookie", "7", "example.com",
+ "/gadgets", base::Time(), base::Time(),
+ /* secure */ false,
+ /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
+ /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
+
+ std::string reply =
+ "{\"url\":\"https://example.com\","
+ " \"http_response_code\":200,"
+ " \"data\":\"Reply\","
+ " \"headers\":{\"Content-Type\":\"text/html; charset=UTF-8\"}}";
+
+ std::unique_ptr<net::URLRequest> request(
+ CreateAndCompleteJob(GURL("https://example.com"), reply));
+
+ std::string expected_request_json =
+ "{\"url\": \"https://example.com/\","
+ " \"method\": \"GET\","
+ " \"headers\": {"
+ " \"Cookie\": \"basic_cookie=1; secure_cookie=2; http_only_cookie=3\","
+ " \"Referer\": \"\""
+ " }"
+ "}";
+
+ EXPECT_THAT(fetch_request_, MatchesJson(expected_request_json));
+}
+
+TEST_F(GenericURLRequestJobTest, DelegateBlocksLoading) {
+ std::string reply =
+ "{\"url\":\"https://example.com\","
+ " \"http_response_code\":200,"
+ " \"data\":\"Reply\","
+ " \"headers\":{\"Content-Type\":\"text/html; charset=UTF-8\"}}";
+
+ job_delegate_.SetShouldBlock(true);
+
+ std::unique_ptr<net::URLRequest> request(
+ CreateAndCompleteJob(GURL("https://example.com"), reply));
+
+ EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status());
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request->status().error());
+}
+
+} // namespace headless
diff --git a/chromium/headless/public/util/http_url_fetcher.cc b/chromium/headless/public/util/http_url_fetcher.cc
new file mode 100644
index 00000000000..cbb2c0ad667
--- /dev/null
+++ b/chromium/headless/public/util/http_url_fetcher.cc
@@ -0,0 +1,187 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/http_url_fetcher.h"
+
+#include "net/base/io_buffer.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+
+namespace headless {
+
+class HttpURLFetcher::Delegate : public net::URLRequest::Delegate {
+ public:
+ Delegate(const GURL& rewritten_url,
+ const std::string& method,
+ const net::HttpRequestHeaders& request_headers,
+ const net::URLRequestContext* url_request_context,
+ ResultListener* result_listener);
+ ~Delegate() override;
+
+ // URLRequest::Delegate methods:
+ void OnAuthRequired(net::URLRequest* request,
+ net::AuthChallengeInfo* auth_info) override;
+ void OnSSLCertificateError(net::URLRequest* request,
+ const net::SSLInfo& ssl_info,
+ bool fatal) override;
+ void OnResponseStarted(net::URLRequest* request, int net_error) override;
+ void OnReadCompleted(net::URLRequest* request, int num_bytes) override;
+
+ private:
+ enum { kBufSize = 4096 };
+
+ bool ConsumeBytesRead(net::URLRequest* request, int num_bytes);
+ void ReadBody(net::URLRequest* request);
+ void OnResponseCompleted(net::URLRequest* request, int net_error);
+
+ // Holds the error condition that was hit by the request, or OK.
+ int result_code_;
+
+ // Buffer that URLRequest writes into.
+ scoped_refptr<net::IOBuffer> buf_;
+
+ // The HTTP fetch.
+ std::unique_ptr<net::URLRequest> request_;
+
+ // Holds the bytes read so far.
+ std::string bytes_read_so_far_;
+
+ // The interface kn which to report any results.
+ ResultListener* result_listener_; // NOT OWNED.
+};
+
+HttpURLFetcher::Delegate::Delegate(
+ const GURL& rewritten_url,
+ const std::string& method,
+ const net::HttpRequestHeaders& request_headers,
+ const net::URLRequestContext* url_request_context,
+ ResultListener* result_listener)
+ : result_code_(net::OK),
+ buf_(new net::IOBuffer(kBufSize)),
+ request_(url_request_context->CreateRequest(rewritten_url,
+ net::DEFAULT_PRIORITY,
+ this)),
+ result_listener_(result_listener) {
+ request_->set_method(method);
+ request_->SetExtraRequestHeaders(request_headers);
+ request_->Start();
+}
+
+HttpURLFetcher::Delegate::~Delegate() {}
+
+void HttpURLFetcher::Delegate::OnAuthRequired(
+ net::URLRequest* request,
+ net::AuthChallengeInfo* auth_info) {
+ DCHECK_EQ(request, request_.get());
+ LOG(WARNING) << "Auth required to fetch URL, aborting.";
+ result_code_ = net::ERR_NOT_IMPLEMENTED;
+ request->CancelAuth();
+}
+
+void HttpURLFetcher::Delegate::OnSSLCertificateError(
+ net::URLRequest* request,
+ const net::SSLInfo& ssl_info,
+ bool fatal) {
+ DCHECK_EQ(request, request_.get());
+
+ // Revocation check failures are not fatal.
+ if (net::IsCertStatusMinorError(ssl_info.cert_status)) {
+ request->ContinueDespiteLastError();
+ return;
+ }
+ LOG(WARNING) << "SSL certificate error, aborting.";
+
+ // Certificate errors are in same space as net errors.
+ result_code_ = net::MapCertStatusToNetError(ssl_info.cert_status);
+ request->Cancel();
+}
+
+void HttpURLFetcher::Delegate::OnResponseStarted(net::URLRequest* request,
+ int net_error) {
+ DCHECK_EQ(request, request_.get());
+ DCHECK_NE(net::ERR_IO_PENDING, net_error);
+
+ if (net_error != net::OK) {
+ OnResponseCompleted(request, net_error);
+ return;
+ }
+
+ ReadBody(request);
+}
+
+void HttpURLFetcher::Delegate::OnReadCompleted(net::URLRequest* request,
+ int num_bytes) {
+ DCHECK_EQ(request, request_.get());
+ DCHECK_NE(net::ERR_IO_PENDING, num_bytes);
+
+ if (ConsumeBytesRead(request, num_bytes)) {
+ // Keep reading.
+ ReadBody(request);
+ }
+}
+
+void HttpURLFetcher::Delegate::ReadBody(net::URLRequest* request) {
+ // Read as many bytes as are available synchronously.
+ while (true) {
+ int num_bytes = request->Read(buf_.get(), kBufSize);
+ if (num_bytes == net::ERR_IO_PENDING)
+ return;
+
+ if (num_bytes < 0) {
+ OnResponseCompleted(request, num_bytes);
+ return;
+ }
+
+ if (!ConsumeBytesRead(request, num_bytes))
+ return;
+ }
+}
+
+bool HttpURLFetcher::Delegate::ConsumeBytesRead(net::URLRequest* request,
+ int num_bytes) {
+ if (num_bytes <= 0) {
+ // Error while reading, or EOF.
+ OnResponseCompleted(request, num_bytes);
+ return false;
+ }
+
+ bytes_read_so_far_.append(buf_->data(), num_bytes);
+ return true;
+}
+
+void HttpURLFetcher::Delegate::OnResponseCompleted(net::URLRequest* request,
+ int net_error) {
+ DCHECK_EQ(request, request_.get());
+
+ if (result_code_ != net::OK) {
+ result_listener_->OnFetchStartError(static_cast<net::Error>(result_code_));
+ return;
+ }
+
+ if (net_error != net::OK) {
+ result_listener_->OnFetchStartError(static_cast<net::Error>(net_error));
+ return;
+ }
+
+ result_listener_->OnFetchCompleteExtractHeaders(
+ request->url(), request->GetResponseCode(), bytes_read_so_far_.c_str(),
+ bytes_read_so_far_.size());
+}
+
+HttpURLFetcher::HttpURLFetcher(
+ const net::URLRequestContext* url_request_context)
+ : url_request_context_(url_request_context) {}
+
+HttpURLFetcher::~HttpURLFetcher() {}
+
+void HttpURLFetcher::StartFetch(const GURL& rewritten_url,
+ const std::string& method,
+ const net::HttpRequestHeaders& request_headers,
+ ResultListener* result_listener) {
+ delegate_.reset(new Delegate(rewritten_url, method, request_headers,
+ url_request_context_, result_listener));
+}
+
+} // namespace headless
diff --git a/chromium/headless/public/util/http_url_fetcher.h b/chromium/headless/public/util/http_url_fetcher.h
new file mode 100644
index 00000000000..2b17287d8fd
--- /dev/null
+++ b/chromium/headless/public/util/http_url_fetcher.h
@@ -0,0 +1,42 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_PUBLIC_UTIL_HTTP_URL_FETCHER_H_
+#define HEADLESS_PUBLIC_UTIL_HTTP_URL_FETCHER_H_
+
+#include "base/macros.h"
+#include "headless/public/util/url_fetcher.h"
+
+namespace net {
+class URLRequestContext;
+class URLRequestJobFactory;
+} // namespace
+
+namespace headless {
+
+// A simple URLFetcher that uses a net::URLRequestContext to as a back end for
+// http(s) fetches.
+class HttpURLFetcher : public URLFetcher {
+ public:
+ explicit HttpURLFetcher(const net::URLRequestContext* url_request_context);
+ ~HttpURLFetcher() override;
+
+ // URLFetcher implementation:
+ void StartFetch(const GURL& rewritten_url,
+ const std::string& method,
+ const net::HttpRequestHeaders& request_headers,
+ ResultListener* result_listener) override;
+
+ private:
+ class Delegate;
+
+ const net::URLRequestContext* url_request_context_;
+ std::unique_ptr<Delegate> delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpURLFetcher);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_HTTP_URL_FETCHER_H_
diff --git a/chromium/headless/public/util/in_memory_protocol_handler.cc b/chromium/headless/public/util/in_memory_protocol_handler.cc
new file mode 100644
index 00000000000..27c076551db
--- /dev/null
+++ b/chromium/headless/public/util/in_memory_protocol_handler.cc
@@ -0,0 +1,37 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/in_memory_protocol_handler.h"
+
+#include "headless/public/util/in_memory_request_job.h"
+
+namespace headless {
+
+InMemoryProtocolHandler::InMemoryProtocolHandler() {}
+InMemoryProtocolHandler::~InMemoryProtocolHandler() {}
+
+net::URLRequestJob* InMemoryProtocolHandler::MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const {
+ return new InMemoryRequestJob(request, network_delegate,
+ GetResponse(request->url().GetContent()));
+}
+
+void InMemoryProtocolHandler::InsertResponse(const std::string& request_path,
+ const Response& response) {
+ // GURL::GetContent excludes the scheme and : but includes the // prefix. As a
+ // convenience we add it here.
+ response_map_["//" + request_path] = response;
+}
+
+const InMemoryProtocolHandler::Response* InMemoryProtocolHandler::GetResponse(
+ const std::string& request_path) const {
+ std::map<std::string, Response>::const_iterator find_it =
+ response_map_.find(request_path);
+ if (find_it == response_map_.end())
+ return nullptr;
+ return &find_it->second;
+}
+
+} // namespace headless
diff --git a/chromium/headless/public/util/in_memory_protocol_handler.h b/chromium/headless/public/util/in_memory_protocol_handler.h
new file mode 100644
index 00000000000..1d69d46a9b2
--- /dev/null
+++ b/chromium/headless/public/util/in_memory_protocol_handler.h
@@ -0,0 +1,46 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_PUBLIC_UTIL_IN_MEMORY_PROTOCOL_HANDLER_H_
+#define HEADLESS_PUBLIC_UTIL_IN_MEMORY_PROTOCOL_HANDLER_H_
+
+#include <map>
+
+#include "net/url_request/url_request_job_factory.h"
+
+namespace headless {
+
+class InMemoryProtocolHandler
+ : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ InMemoryProtocolHandler();
+ ~InMemoryProtocolHandler() override;
+
+ net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override;
+
+ struct Response {
+ Response() {}
+ Response(const std::string& data, const std::string& mime_type)
+ : data(data), mime_type(mime_type) {}
+
+ std::string data;
+ std::string mime_type;
+ };
+
+ void InsertResponse(const std::string& request_path,
+ const Response& response);
+
+ private:
+ const Response* GetResponse(const std::string& request_path) const;
+
+ std::map<std::string, Response> response_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(InMemoryProtocolHandler);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_IN_MEMORY_PROTOCOL_HANDLER_H_
diff --git a/chromium/headless/public/util/in_memory_request_job.cc b/chromium/headless/public/util/in_memory_request_job.cc
new file mode 100644
index 00000000000..5fa84151980
--- /dev/null
+++ b/chromium/headless/public/util/in_memory_request_job.cc
@@ -0,0 +1,65 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/in_memory_request_job.h"
+
+#include "base/threading/thread_task_runner_handle.h"
+#include "headless/public/util/in_memory_protocol_handler.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request_status.h"
+
+namespace headless {
+
+InMemoryRequestJob::InMemoryRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const InMemoryProtocolHandler::Response* response)
+ : net::URLRequestJob(request, network_delegate),
+ response_(response),
+ data_offset_(0),
+ weak_factory_(this) {}
+
+InMemoryRequestJob::~InMemoryRequestJob() {}
+
+void InMemoryRequestJob::Start() {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&InMemoryRequestJob::StartAsync, weak_factory_.GetWeakPtr()));
+}
+
+void InMemoryRequestJob::StartAsync() {
+ if (!response_) {
+ NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
+ net::ERR_FILE_NOT_FOUND));
+ return;
+ }
+ NotifyHeadersComplete();
+}
+
+void InMemoryRequestJob::Kill() {
+ weak_factory_.InvalidateWeakPtrs();
+ URLRequestJob::Kill();
+}
+
+int InMemoryRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size) {
+ DCHECK(response_);
+ DCHECK_LE(data_offset_, response_->data.size());
+ int remaining =
+ base::checked_cast<int>(response_->data.size() - data_offset_);
+ if (buf_size > remaining)
+ buf_size = remaining;
+ memcpy(buf->data(), response_->data.data() + data_offset_, buf_size);
+ data_offset_ += buf_size;
+ return buf_size;
+}
+
+bool InMemoryRequestJob::GetMimeType(std::string* mime_type) const {
+ *mime_type = response_->mime_type;
+ return true;
+}
+
+} // namespace headless
diff --git a/chromium/headless/public/util/in_memory_request_job.h b/chromium/headless/public/util/in_memory_request_job.h
new file mode 100644
index 00000000000..1526c9a470c
--- /dev/null
+++ b/chromium/headless/public/util/in_memory_request_job.h
@@ -0,0 +1,45 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_PUBLIC_UTIL_IN_MEMORY_URL_REQUEST_JOB_H_
+#define HEADLESS_PUBLIC_UTIL_IN_MEMORY_URL_REQUEST_JOB_H_
+
+#include "base/memory/weak_ptr.h"
+#include "headless/public/util/in_memory_protocol_handler.h"
+#include "net/url_request/url_request_job.h"
+
+namespace net {
+class StringIOBuffer;
+class DrainableIOBuffer;
+}
+
+namespace headless {
+class InMemoryRequestJob : public net::URLRequestJob {
+ public:
+ InMemoryRequestJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const InMemoryProtocolHandler::Response* response);
+
+ // net::URLRequestJob implementation:
+ void Start() override;
+ void Kill() override;
+ int ReadRawData(net::IOBuffer* buf, int buf_size) override;
+ bool GetMimeType(std::string* mime_type) const override;
+
+ private:
+ ~InMemoryRequestJob() override;
+
+ void StartAsync();
+
+ const InMemoryProtocolHandler::Response* response_; // NOT OWNED
+ size_t data_offset_;
+
+ base::WeakPtrFactory<InMemoryRequestJob> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(InMemoryRequestJob);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_IN_MEMORY_URL_REQUEST_JOB_H_
diff --git a/chromium/headless/public/util/managed_dispatch_url_request_job.cc b/chromium/headless/public/util/managed_dispatch_url_request_job.cc
new file mode 100644
index 00000000000..2f79373af0d
--- /dev/null
+++ b/chromium/headless/public/util/managed_dispatch_url_request_job.cc
@@ -0,0 +1,47 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/managed_dispatch_url_request_job.h"
+
+#include "headless/public/util/url_request_dispatcher.h"
+
+namespace headless {
+
+ManagedDispatchURLRequestJob::ManagedDispatchURLRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ URLRequestDispatcher* url_request_dispatcher)
+ : net::URLRequestJob(request, network_delegate),
+ url_request_dispatcher_(url_request_dispatcher) {
+ url_request_dispatcher_->JobCreated(this);
+}
+
+ManagedDispatchURLRequestJob::~ManagedDispatchURLRequestJob() {
+ // We can be fairly certain the renderer has finished by the time the job gets
+ // deleted.
+ url_request_dispatcher_->JobDeleted(this);
+}
+
+void ManagedDispatchURLRequestJob::Kill() {
+ url_request_dispatcher_->JobKilled(this);
+ URLRequestJob::Kill();
+}
+
+void ManagedDispatchURLRequestJob::DispatchStartError(net::Error error) {
+ url_request_dispatcher_->JobFailed(this, error);
+}
+
+void ManagedDispatchURLRequestJob::DispatchHeadersComplete() {
+ url_request_dispatcher_->DataReady(this);
+}
+
+void ManagedDispatchURLRequestJob::OnHeadersComplete() {
+ NotifyHeadersComplete();
+}
+
+void ManagedDispatchURLRequestJob::OnStartError(net::Error error) {
+ NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED, error));
+}
+
+} // namespace headless
diff --git a/chromium/headless/public/util/managed_dispatch_url_request_job.h b/chromium/headless/public/util/managed_dispatch_url_request_job.h
new file mode 100644
index 00000000000..3903d2a96a0
--- /dev/null
+++ b/chromium/headless/public/util/managed_dispatch_url_request_job.h
@@ -0,0 +1,61 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_PUBLIC_UTIL_MANAGED_DISPATCH_URL_REQUEST_JOB_H_
+#define HEADLESS_PUBLIC_UTIL_MANAGED_DISPATCH_URL_REQUEST_JOB_H_
+
+#include "base/macros.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+
+namespace headless {
+class URLRequestDispatcher;
+
+// A ManagedDispatchURLRequestJob exists to allow a URLRequestDispatcher control
+// the order in which a set of fetches complete. Typically this is done to make
+// fetching deterministic. NOTE URLRequestDispatcher sub classes use
+// OnHeadersComplete and OnStartError to drive the URLRequestJob.
+class ManagedDispatchURLRequestJob : public net::URLRequestJob {
+ public:
+ ManagedDispatchURLRequestJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ URLRequestDispatcher* url_request_dispatcher);
+
+ ~ManagedDispatchURLRequestJob() override;
+
+ // Tells the net::URLRequestJob that the data has been fetched and is ready to
+ // be consumed.
+ // Virtual for FakeManagedDispatchURLRequestJob.
+ virtual void OnHeadersComplete();
+
+ // Tells the net::URLRequestJob that the fetch failed.
+ // Virtual for FakeManagedDispatchURLRequestJob.
+ virtual void OnStartError(net::Error error);
+
+ protected:
+ // net::URLRequestJob implementation:
+ void Kill() override;
+
+ // Tell the dispatcher the request failed.
+ virtual void DispatchStartError(net::Error error);
+
+ // Tell the dispatcher the data is ready for the net stack to consume.
+ virtual void DispatchHeadersComplete();
+
+ URLRequestDispatcher* url_request_dispatcher_; // Not owned.
+
+ private:
+ // Derived classes should not use NotifyHeadersComplete or NotifyStartError
+ // directly. Instead they should use DispatchHeadersComplete or
+ // DispatchStartError.
+ using URLRequestJob::NotifyHeadersComplete;
+ using URLRequestJob::NotifyStartError;
+
+ DISALLOW_COPY_AND_ASSIGN(ManagedDispatchURLRequestJob);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_MANAGED_DISPATCH_URL_REQUEST_JOB_H_
diff --git a/chromium/headless/public/util/testing/fake_managed_dispatch_url_request_job.cc b/chromium/headless/public/util/testing/fake_managed_dispatch_url_request_job.cc
new file mode 100644
index 00000000000..24191d3f026
--- /dev/null
+++ b/chromium/headless/public/util/testing/fake_managed_dispatch_url_request_job.cc
@@ -0,0 +1,17 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/testing/fake_managed_dispatch_url_request_job.h"
+
+namespace headless {
+
+void FakeManagedDispatchURLRequestJob::OnHeadersComplete() {
+ notifications_->push_back(base::StringPrintf("id: %d OK", id_));
+}
+
+void FakeManagedDispatchURLRequestJob::OnStartError(net::Error error) {
+ notifications_->push_back(base::StringPrintf("id: %d err: %d", id_, error));
+}
+
+} // namespace headless
diff --git a/chromium/headless/public/util/testing/fake_managed_dispatch_url_request_job.h b/chromium/headless/public/util/testing/fake_managed_dispatch_url_request_job.h
new file mode 100644
index 00000000000..cb0dabdf5b1
--- /dev/null
+++ b/chromium/headless/public/util/testing/fake_managed_dispatch_url_request_job.h
@@ -0,0 +1,49 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_PUBLIC_UTIL_TESTING_FAKE_MANAGED_DISPATCH_URL_REQUEST_JOB_H_
+#define HEADLESS_PUBLIC_UTIL_TESTING_FAKE_MANAGED_DISPATCH_URL_REQUEST_JOB_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "headless/public/util/managed_dispatch_url_request_job.h"
+#include "net/base/net_errors.h"
+
+namespace headless {
+
+class URLRequestDispatcher;
+
+class FakeManagedDispatchURLRequestJob : public ManagedDispatchURLRequestJob {
+ public:
+ FakeManagedDispatchURLRequestJob(URLRequestDispatcher* url_request_dispatcher,
+ int id,
+ std::vector<std::string>* notifications)
+ : ManagedDispatchURLRequestJob(nullptr, nullptr, url_request_dispatcher),
+ id_(id),
+ notifications_(notifications) {}
+
+ ~FakeManagedDispatchURLRequestJob() override {}
+
+ using ManagedDispatchURLRequestJob::DispatchHeadersComplete;
+ using ManagedDispatchURLRequestJob::DispatchStartError;
+
+ void Start() override {}
+
+ void OnHeadersComplete() override;
+
+ void OnStartError(net::Error error) override;
+
+ private:
+ int id_;
+ std::vector<std::string>* notifications_; // NOT OWNED
+
+ DISALLOW_COPY_AND_ASSIGN(FakeManagedDispatchURLRequestJob);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_TESTING_FAKE_MANAGED_DISPATCH_URL_REQUEST_JOB_H_
diff --git a/chromium/headless/public/util/testing/generic_url_request_mocks.cc b/chromium/headless/public/util/testing/generic_url_request_mocks.cc
new file mode 100644
index 00000000000..08ff33d4b07
--- /dev/null
+++ b/chromium/headless/public/util/testing/generic_url_request_mocks.cc
@@ -0,0 +1,172 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/testing/generic_url_request_mocks.h"
+
+#include "base/logging.h"
+
+namespace net {
+class URLRequestJob;
+} // namespace net
+
+namespace headless {
+
+// MockGenericURLRequestJobDelegate
+MockGenericURLRequestJobDelegate::MockGenericURLRequestJobDelegate()
+ : should_block_(false) {}
+MockGenericURLRequestJobDelegate::~MockGenericURLRequestJobDelegate() {}
+
+bool MockGenericURLRequestJobDelegate::BlockOrRewriteRequest(
+ const GURL& url,
+ const std::string& method,
+ const std::string& referrer,
+ GenericURLRequestJob::RewriteCallback callback) {
+ if (should_block_)
+ callback(GenericURLRequestJob::RewriteResult::kDeny, GURL(), method);
+ return should_block_;
+}
+
+const GenericURLRequestJob::HttpResponse*
+MockGenericURLRequestJobDelegate::MaybeMatchResource(
+ const GURL& url,
+ const std::string& method,
+ const net::HttpRequestHeaders& request_headers) {
+ return nullptr;
+}
+
+void MockGenericURLRequestJobDelegate::OnResourceLoadComplete(
+ const GURL& final_url,
+ const std::string& mime_type,
+ int http_response_code) {}
+
+// MockCookieStore
+MockCookieStore::MockCookieStore() {}
+MockCookieStore::~MockCookieStore() {}
+
+void MockCookieStore::SetCookieWithOptionsAsync(
+ const GURL& url,
+ const std::string& cookie_line,
+ const net::CookieOptions& options,
+ const SetCookiesCallback& callback) {
+ CHECK(false);
+}
+
+void MockCookieStore::SetCookieWithDetailsAsync(
+ const GURL& url,
+ const std::string& name,
+ const std::string& value,
+ const std::string& domain,
+ const std::string& path,
+ base::Time creation_time,
+ base::Time expiration_time,
+ base::Time last_access_time,
+ bool secure,
+ bool http_only,
+ net::CookieSameSite same_site,
+ bool enforce_strict_secure,
+ net::CookiePriority priority,
+ const SetCookiesCallback& callback) {
+ CHECK(false);
+}
+
+void MockCookieStore::GetCookiesWithOptionsAsync(
+ const GURL& url,
+ const net::CookieOptions& options,
+ const GetCookiesCallback& callback) {
+ CHECK(false);
+}
+
+void MockCookieStore::GetCookieListWithOptionsAsync(
+ const GURL& url,
+ const net::CookieOptions& options,
+ const GetCookieListCallback& callback) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&MockCookieStore::SendCookies,
+ base::Unretained(this), url, options, callback));
+}
+
+void MockCookieStore::GetAllCookiesAsync(
+ const GetCookieListCallback& callback) {
+ CHECK(false);
+}
+
+void MockCookieStore::DeleteCookieAsync(const GURL& url,
+ const std::string& cookie_name,
+ const base::Closure& callback) {
+ CHECK(false);
+}
+
+void MockCookieStore::DeleteCanonicalCookieAsync(
+ const net::CanonicalCookie& cookie,
+ const DeleteCallback& callback) {
+ CHECK(false);
+}
+
+void MockCookieStore::DeleteAllCreatedBetweenAsync(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ const DeleteCallback& callback) {
+ CHECK(false);
+}
+
+void MockCookieStore::DeleteAllCreatedBetweenWithPredicateAsync(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ const CookiePredicate& predicate,
+ const DeleteCallback& callback) {
+ CHECK(false);
+}
+
+void MockCookieStore::DeleteSessionCookiesAsync(const DeleteCallback&) {
+ CHECK(false);
+}
+
+void MockCookieStore::FlushStore(const base::Closure& callback) {
+ CHECK(false);
+}
+
+void MockCookieStore::SetForceKeepSessionState() {
+ CHECK(false);
+}
+
+std::unique_ptr<net::CookieStore::CookieChangedSubscription>
+MockCookieStore::AddCallbackForCookie(const GURL& url,
+ const std::string& name,
+ const CookieChangedCallback& callback) {
+ CHECK(false);
+ return nullptr;
+}
+
+bool MockCookieStore::IsEphemeral() {
+ CHECK(false);
+ return true;
+}
+
+void MockCookieStore::SendCookies(const GURL& url,
+ const net::CookieOptions& options,
+ const GetCookieListCallback& callback) {
+ net::CookieList result;
+ for (const auto& cookie : cookies_) {
+ if (cookie.IncludeForRequestURL(url, options))
+ result.push_back(cookie);
+ }
+ callback.Run(result);
+}
+
+// MockURLRequestDelegate
+MockURLRequestDelegate::MockURLRequestDelegate() {}
+MockURLRequestDelegate::~MockURLRequestDelegate() {}
+
+void MockURLRequestDelegate::OnResponseStarted(net::URLRequest* request) {}
+void MockURLRequestDelegate::OnReadCompleted(net::URLRequest* request,
+ int bytes_read) {}
+const std::string& MockURLRequestDelegate::response_data() const {
+ return response_data_;
+}
+
+const net::IOBufferWithSize* MockURLRequestDelegate::metadata() const {
+ return nullptr;
+}
+
+} // namespace headless
diff --git a/chromium/headless/public/util/testing/generic_url_request_mocks.h b/chromium/headless/public/util/testing/generic_url_request_mocks.h
new file mode 100644
index 00000000000..7dc683fce64
--- /dev/null
+++ b/chromium/headless/public/util/testing/generic_url_request_mocks.h
@@ -0,0 +1,155 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_PUBLIC_UTIL_TESTING_GENERIC_URL_REQUEST_MOCKS_H_
+#define HEADLESS_PUBLIC_UTIL_TESTING_GENERIC_URL_REQUEST_MOCKS_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "headless/public/util/generic_url_request_job.h"
+#include "headless/public/util/testing/generic_url_request_mocks.h"
+#include "net/cookies/cookie_store.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job_factory.h"
+
+namespace net {
+class URLRequestJob;
+} // namespace net
+
+namespace htmlrender_webkit_headless_proto {
+class Resource;
+} // htmlrender_webkit_headless_proto net
+
+namespace headless {
+
+class MockGenericURLRequestJobDelegate : public GenericURLRequestJob::Delegate {
+ public:
+ MockGenericURLRequestJobDelegate();
+ ~MockGenericURLRequestJobDelegate() override;
+
+ bool BlockOrRewriteRequest(
+ const GURL& url,
+ const std::string& method,
+ const std::string& referrer,
+ GenericURLRequestJob::RewriteCallback callback) override;
+
+ const GenericURLRequestJob::HttpResponse* MaybeMatchResource(
+ const GURL& url,
+ const std::string& method,
+ const net::HttpRequestHeaders& request_headers) override;
+
+ void OnResourceLoadComplete(const GURL& final_url,
+ const std::string& mime_type,
+ int http_response_code) override;
+
+ void SetShouldBlock(bool should_block) { should_block_ = should_block; }
+
+ private:
+ bool should_block_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockGenericURLRequestJobDelegate);
+};
+
+// TODO(alexclarke): We may be able to replace this with the CookieMonster.
+class MockCookieStore : public net::CookieStore {
+ public:
+ MockCookieStore();
+ ~MockCookieStore() override;
+
+ // net::CookieStore implementation:
+ void SetCookieWithOptionsAsync(const GURL& url,
+ const std::string& cookie_line,
+ const net::CookieOptions& options,
+ const SetCookiesCallback& callback) override;
+
+ void SetCookieWithDetailsAsync(const GURL& url,
+ const std::string& name,
+ const std::string& value,
+ const std::string& domain,
+ const std::string& path,
+ base::Time creation_time,
+ base::Time expiration_time,
+ base::Time last_access_time,
+ bool secure,
+ bool http_only,
+ net::CookieSameSite same_site,
+ bool enforce_strict_secure,
+ net::CookiePriority priority,
+ const SetCookiesCallback& callback) override;
+
+ void GetCookiesWithOptionsAsync(const GURL& url,
+ const net::CookieOptions& options,
+ const GetCookiesCallback& callback) override;
+
+ void GetCookieListWithOptionsAsync(
+ const GURL& url,
+ const net::CookieOptions& options,
+ const GetCookieListCallback& callback) override;
+
+ void GetAllCookiesAsync(const GetCookieListCallback& callback) override;
+
+ void DeleteCookieAsync(const GURL& url,
+ const std::string& cookie_name,
+ const base::Closure& callback) override;
+
+ void DeleteCanonicalCookieAsync(const net::CanonicalCookie& cookie,
+ const DeleteCallback& callback) override;
+
+ void DeleteAllCreatedBetweenAsync(const base::Time& delete_begin,
+ const base::Time& delete_end,
+ const DeleteCallback& callback) override;
+
+ void DeleteAllCreatedBetweenWithPredicateAsync(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ const CookiePredicate& predicate,
+ const DeleteCallback& callback) override;
+
+ void DeleteSessionCookiesAsync(const DeleteCallback&) override;
+
+ void FlushStore(const base::Closure& callback) override;
+
+ void SetForceKeepSessionState() override;
+
+ std::unique_ptr<CookieChangedSubscription> AddCallbackForCookie(
+ const GURL& url,
+ const std::string& name,
+ const CookieChangedCallback& callback) override;
+
+ bool IsEphemeral() override;
+
+ net::CookieList* cookies() { return &cookies_; }
+
+ private:
+ void SendCookies(const GURL& url,
+ const net::CookieOptions& options,
+ const GetCookieListCallback& callback);
+
+ net::CookieList cookies_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockCookieStore);
+};
+
+class MockURLRequestDelegate : public net::URLRequest::Delegate {
+ public:
+ MockURLRequestDelegate();
+ ~MockURLRequestDelegate() override;
+
+ void OnResponseStarted(net::URLRequest* request) override;
+ void OnReadCompleted(net::URLRequest* request, int bytes_read) override;
+ const std::string& response_data() const;
+ const net::IOBufferWithSize* metadata() const;
+
+ private:
+ std::string response_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockURLRequestDelegate);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_TESTING_GENERIC_URL_REQUEST_MOCKS_H_
diff --git a/chromium/headless/public/util/url_fetcher.cc b/chromium/headless/public/util/url_fetcher.cc
new file mode 100644
index 00000000000..8d8d33fc239
--- /dev/null
+++ b/chromium/headless/public/util/url_fetcher.cc
@@ -0,0 +1,40 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/url_fetcher.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+
+namespace headless {
+
+void URLFetcher::ResultListener::OnFetchCompleteExtractHeaders(
+ const GURL& final_url,
+ int http_response_code,
+ const char* response_data,
+ size_t response_data_size) {
+ size_t read_offset = 0;
+ int header_size =
+ net::HttpUtil::LocateEndOfHeaders(response_data, response_data_size);
+ scoped_refptr<net::HttpResponseHeaders> response_headers;
+
+ if (header_size == -1) {
+ LOG(WARNING) << "Can't find headers in result for url: " << final_url;
+ response_headers = new net::HttpResponseHeaders("");
+ } else {
+ response_headers = new net::HttpResponseHeaders(
+ net::HttpUtil::AssembleRawHeaders(response_data, header_size));
+ read_offset = header_size;
+ }
+
+ CHECK_LE(read_offset, response_data_size);
+ OnFetchComplete(final_url, http_response_code, std::move(response_headers),
+ response_data + read_offset,
+ response_data_size - read_offset);
+}
+
+} // namespace headless
diff --git a/chromium/headless/public/util/url_fetcher.h b/chromium/headless/public/util/url_fetcher.h
new file mode 100644
index 00000000000..ac4bbc51426
--- /dev/null
+++ b/chromium/headless/public/util/url_fetcher.h
@@ -0,0 +1,74 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_PUBLIC_UTIL_URL_FETCHER_H_
+#define HEADLESS_PUBLIC_UTIL_URL_FETCHER_H_
+
+#include <stddef.h>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/net_errors.h"
+#include "url/gurl.h"
+
+namespace net {
+class HttpRequestHeaders;
+class HttpResponseHeaders;
+} // namespace net
+
+namespace headless {
+
+// An interface for fetching URLs. Note these are only intended to be used once.
+class URLFetcher {
+ public:
+ URLFetcher() {}
+ virtual ~URLFetcher() {}
+
+ // Interface for reporting the fetch result.
+ class ResultListener {
+ public:
+ ResultListener() {}
+
+ // Informs the listener that the fetch failed.
+ virtual void OnFetchStartError(net::Error error) = 0;
+
+ // Informs the listener that the fetch succeeded and returns the HTTP
+ // headers and the body. NOTE |body| must be owned by the caller and remain
+ // valid until the fetcher is destroyed,
+ virtual void OnFetchComplete(
+ const GURL& final_url,
+ int http_response_code,
+ scoped_refptr<net::HttpResponseHeaders> response_headers,
+ const char* body,
+ size_t body_size) = 0;
+
+ // Helper function which extracts the headers from |response_data| and calls
+ // OnFetchComplete.
+ void OnFetchCompleteExtractHeaders(const GURL& final_url,
+ int http_response_code,
+ const char* response_data,
+ size_t response_data_size);
+
+ protected:
+ virtual ~ResultListener() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResultListener);
+ };
+
+ // Instructs the sub-class to fetch the resource.
+ virtual void StartFetch(const GURL& rewritten_url,
+ const std::string& method,
+ const net::HttpRequestHeaders& request_headers,
+ ResultListener* result_listener) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(URLFetcher);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_URL_FETCHER_H_
diff --git a/chromium/headless/public/util/url_request_dispatcher.h b/chromium/headless/public/util/url_request_dispatcher.h
new file mode 100644
index 00000000000..1f34509a3a5
--- /dev/null
+++ b/chromium/headless/public/util/url_request_dispatcher.h
@@ -0,0 +1,45 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_PUBLIC_UTIL_URL_REQUEST_DISPATCHER_H_
+#define HEADLESS_PUBLIC_UTIL_URL_REQUEST_DISPATCHER_H_
+
+#include "base/macros.h"
+#include "net/base/net_errors.h"
+
+namespace headless {
+class ManagedDispatchURLRequestJob;
+
+// Interface to abstract and potentially reorder (for determinism) calls to
+// ManagedDispatchUrlRequestJob::OnHeadersComplete and
+// ManagedDispatchUrlRequestJob::NotifyStartError.
+class URLRequestDispatcher {
+ public:
+ URLRequestDispatcher() {}
+ virtual ~URLRequestDispatcher() {}
+
+ // Tells us a URLRequestJob was created. Can be called from any thread.
+ virtual void JobCreated(ManagedDispatchURLRequestJob* job) = 0;
+
+ // Tells us a URLRequestJob got killed. Can be called from any thread.
+ virtual void JobKilled(ManagedDispatchURLRequestJob* job) = 0;
+
+ // Tells us a URLRequestJob failed. Can be called from any thread.
+ virtual void JobFailed(ManagedDispatchURLRequestJob* job,
+ net::Error error) = 0;
+
+ // Tells us a URLRequestJob has fetched the data and is ready for the net
+ // stack to consume it. Can be called from any thread.
+ virtual void DataReady(ManagedDispatchURLRequestJob* job) = 0;
+
+ // Tells us the job has finished. Can be called from any thread.
+ virtual void JobDeleted(ManagedDispatchURLRequestJob* job) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(URLRequestDispatcher);
+};
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_URL_REQUEST_DISPATCHER_H_
diff --git a/chromium/headless/public/util/user_agent.cc b/chromium/headless/public/util/user_agent.cc
new file mode 100644
index 00000000000..13ffe9fb62a
--- /dev/null
+++ b/chromium/headless/public/util/user_agent.cc
@@ -0,0 +1,20 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/user_agent.h"
+
+#include "content/public/common/user_agent.h"
+
+namespace headless {
+
+std::string BuildUserAgentFromProduct(const std::string& product) {
+ return content::BuildUserAgentFromProduct(product);
+}
+
+std::string BuildUserAgentFromOSAndProduct(const std::string& os_info,
+ const std::string& product) {
+ return content::BuildUserAgentFromOSAndProduct(os_info, product);
+}
+
+} // namespace headless
diff --git a/chromium/headless/public/util/user_agent.h b/chromium/headless/public/util/user_agent.h
new file mode 100644
index 00000000000..04f7f8636c8
--- /dev/null
+++ b/chromium/headless/public/util/user_agent.h
@@ -0,0 +1,18 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_PUBLIC_UTIL_USER_AGENT_H_
+#define HEADLESS_PUBLIC_UTIL_USER_AGENT_H_
+
+#include <string>
+
+namespace headless {
+
+std::string BuildUserAgentFromProduct(const std::string& product);
+std::string BuildUserAgentFromOSAndProduct(const std::string& os_info,
+ const std::string& product);
+
+} // namespace headless
+
+#endif // HEADLESS_PUBLIC_UTIL_USER_AGENT_H_