diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-11-20 10:33:36 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-11-22 11:45:12 +0000 |
commit | be59a35641616a4cf23c4a13fa0632624b021c1b (patch) | |
tree | 9da183258bdf9cc413f7562079d25ace6955467f /chromium/headless | |
parent | d702e4b6a64574e97fc7df8fe3238cde70242080 (diff) | |
download | qtwebengine-chromium-be59a35641616a4cf23c4a13fa0632624b021c1b.tar.gz |
BASELINE: Update Chromium to 62.0.3202.101
Change-Id: I2d5eca8117600df6d331f6166ab24d943d9814ac
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/headless')
68 files changed, 1340 insertions, 662 deletions
diff --git a/chromium/headless/BUILD.gn b/chromium/headless/BUILD.gn index d723d947dce..84cad42d18b 100644 --- a/chromium/headless/BUILD.gn +++ b/chromium/headless/BUILD.gn @@ -7,6 +7,7 @@ import("//build/util/process_version.gni") import("//headless/headless.gni") import("//mojo/public/tools/bindings/mojom.gni") import("//printing/features/features.gni") +import("//services/service_manager/public/service_manifest.gni") import("//testing/test.gni") import("//third_party/closure_compiler/compile_js.gni") import("//tools/grit/grit_rule.gni") @@ -28,8 +29,8 @@ group("headless_lib") { repack("pak") { sources = [ - "$root_gen_dir/blink/public/resources/blink_image_resources_100_percent.pak", "$root_gen_dir/blink/public/resources/blink_resources.pak", + "$root_gen_dir/blink/public/resources/blink_scaled_resources_100_percent.pak", "$root_gen_dir/components/components_resources.pak", "$root_gen_dir/components/strings/components_strings_en-US.pak", "$root_gen_dir/content/app/resources/content_resources_100_percent.pak", @@ -61,8 +62,8 @@ repack("pak") { "//content/browser/devtools:resources", "//content/browser/tracing:resources", "//net:net_resources", - "//third_party/WebKit/public:image_resources", "//third_party/WebKit/public:resources", + "//third_party/WebKit/public:scaled_resources_100_percent", "//ui/resources", "//ui/strings", ] @@ -115,17 +116,30 @@ mojom("headless_render_frame_controller") { ] } +service_manifest("headless_browser_manifest_overlay") { + source = "lib/browser/headless_browser_manifest_overlay.json" +} + +service_manifest("headless_packaged_services_manifest_overlay") { + source = "lib/browser/headless_packaged_services_manifest_overlay.json" + packaged_services = + [ "//components/printing/service:pdf_compositor_manifest" ] +} + grit("resources") { source = "lib/resources/headless_lib_resources.grd" outputs = [ "grit/headless_lib_resources.h", "$root_gen_dir/headless/headless_lib_resources.pak", ] + source_is_generated = true grit_flags = [ "-E", "mojom_root=" + rebase_path(root_gen_dir, root_build_dir), ] deps = [ + ":headless_browser_manifest_overlay", + ":headless_packaged_services_manifest_overlay", ":headless_render_frame_controller_js", ":tab_socket_js", ] @@ -223,7 +237,7 @@ js_library("js_devtools_bindings_lib") { extra_deps = [ ":gen_devtools_client_api" ] } -if (headless_fontconfig_utils) { +if (headless_fontconfig_utils && !is_fuchsia) { static_library("headless_fontconfig_utils") { sources = [ "public/util/fontconfig.cc", @@ -280,8 +294,6 @@ component("headless") { "lib/browser/headless_window_tree_host.h", "lib/headless_content_client.cc", "lib/headless_content_client.h", - "lib/headless_crash_reporter_client.cc", - "lib/headless_crash_reporter_client.h", "public/headless_browser.cc", "public/headless_browser.h", "public/headless_browser_context.h", @@ -323,6 +335,13 @@ component("headless") { "public/util/user_agent.h", ] + if (!is_fuchsia) { + sources += [ + "lib/headless_crash_reporter_client.cc", + "lib/headless_crash_reporter_client.h", + ] + } + sources += generated_devtools_api if (use_aura) { @@ -344,8 +363,8 @@ component("headless") { sources += [ "lib/browser/headless_print_manager.cc", "lib/browser/headless_print_manager.h", - "lib/renderer/headless_print_web_view_helper_delegate.cc", - "lib/renderer/headless_print_web_view_helper_delegate.h", + "lib/renderer/headless_print_render_frame_helper_delegate.cc", + "lib/renderer/headless_print_render_frame_helper_delegate.h", ] } @@ -388,18 +407,31 @@ component("headless") { "lib/renderer/headless_render_frame_controller_impl.h", "lib/renderer/headless_tab_socket_bindings.cc", "lib/renderer/headless_tab_socket_bindings.h", + "lib/utility/headless_content_utility_client.cc", + "lib/utility/headless_content_utility_client.h", ] deps += [ - "//components/crash/content/browser", "//components/security_state/content", + "//gin", + "//third_party/WebKit/public:blink", "//third_party/WebKit/public:blink_headers", + "//v8", ] + if (!is_fuchsia) { + deps += [ "//components/crash/content/browser" ] + } + if (is_win) { + deps += [ "//components/crash/content/app:crash_export_thunks" ] + } + if (enable_basic_printing) { deps += [ "//components/printing/browser", "//components/printing/renderer", + "//components/printing/service/public/cpp:factory", + "//components/printing/service/public/interfaces", ] } } @@ -424,7 +456,7 @@ component("headless") { deps += [ "//ui/ozone" ] } - if (headless_fontconfig_utils) { + if (headless_fontconfig_utils && !is_fuchsia) { deps += [ ":headless_fontconfig_utils" ] } @@ -496,6 +528,8 @@ test("headless_unittests") { sources += [ "lib/browser/headless_content_browser_client.cc", "lib/browser/headless_content_browser_client.h", + "lib/utility/headless_content_utility_client.cc", + "lib/utility/headless_content_utility_client.h", ] } @@ -503,7 +537,6 @@ test("headless_unittests") { ":headless_renderer", "//base/test:run_all_unittests", "//base/test:test_support", - "//components/crash/content/browser", "//components/security_state/content", "//content/public/app:both", "//content/public/child:child", @@ -512,9 +545,21 @@ test("headless_unittests") { "//testing/gtest", ] + if (!is_fuchsia) { + deps += [ "//components/crash/content/browser" ] + } + + if (is_win) { + deps += [ "//components/crash/content/app:crash_export_thunks" ] + } + if (enable_basic_printing) { sources += [ "lib/browser/headless_printing_unittest.cc" ] - deps += [ "//components/printing/browser" ] + deps += [ + "//components/printing/browser", + "//components/printing/service/public/cpp:factory", + "//third_party/WebKit/public:blink", + ] } } @@ -638,19 +683,27 @@ test("headless_browsertests") { sources += [ "lib/browser/headless_content_browser_client.cc", "lib/browser/headless_content_browser_client.h", + "lib/utility/headless_content_utility_client.cc", + "lib/utility/headless_content_utility_client.h", ] } deps = [ ":headless_renderer", "//base", - "//components/crash/content/browser", "//components/security_state/content", "//content/test:test_support", "//testing/gmock", "//testing/gtest", ] + if (!is_fuchsia) { + deps += [ + "//components/crash/content/app:test_support", + "//components/crash/content/browser", + ] + } + # Only include this if we built the js_binary if (is_linux) { data += [ "$root_out_dir/headless_browser_tests.pak" ] @@ -661,6 +714,7 @@ test("headless_browsertests") { if (enable_basic_printing) { deps += [ "//components/printing/browser", + "//components/printing/service/public/cpp:factory", "//pdf", ] } @@ -734,6 +788,8 @@ if (is_win) { "lib/headless_content_main_delegate.cc", "lib/headless_content_main_delegate.h", "lib/headless_content_main_delegate_win.cc", + "lib/utility/headless_content_utility_client.cc", + "lib/utility/headless_content_utility_client.h", ] deps += [ "//third_party/WebKit/public:blink_headers" ] } @@ -754,12 +810,13 @@ static_library("headless_shell_lib") { "app/shell_navigation_request.h", "lib/browser/headless_content_browser_client.cc", "lib/browser/headless_content_browser_client.h", + "lib/utility/headless_content_utility_client.cc", + "lib/utility/headless_content_utility_client.h", "public/headless_shell.h", ] deps = [ ":headless_renderer", - "//components/crash/content/browser", "//components/security_state/content", "//content/public/app:both", "//content/public/browser", @@ -767,10 +824,15 @@ static_library("headless_shell_lib") { "//content/public/common", ] + if (!is_fuchsia) { + deps += [ "//components/crash/content/browser" ] + } + if (enable_basic_printing) { deps += [ "//components/printing/browser", "//components/printing/renderer", + "//components/printing/service/public/cpp:factory", ] } @@ -778,6 +840,7 @@ static_library("headless_shell_lib") { defines = [ "HEADLESS_USE_CRASPHAD" ] deps += [ + "//components/crash/content/app:crash_export_thunks", "//components/crash/content/app:run_as_crashpad_handler", "//content:sandbox_helper_win", "//sandbox", @@ -785,6 +848,12 @@ static_library("headless_shell_lib") { } } +if (is_fuchsia) { + fuchsia_executable_runner("headless_shell_fuchsia") { + exe_target = ":headless_shell" + } +} + executable("headless_shell") { sources = [ "app/headless_shell_main.cc", diff --git a/chromium/headless/DEPS b/chromium/headless/DEPS index 70959d7e827..343e456ad58 100644 --- a/chromium/headless/DEPS +++ b/chromium/headless/DEPS @@ -1,6 +1,8 @@ include_rules = [ "+components/crash/content/app", "+components/crash/content/browser", + "+components/printing/service/public/cpp", + "+components/printing/service/public/interfaces", "+content/public/app", "+content/public/browser", "+content/public/renderer", @@ -8,6 +10,7 @@ include_rules = [ "+content/public/test", "+mojo/public", "+net", + "+printing/features", "+ui/base", "+ui/base/resource", "+ui/display", diff --git a/chromium/headless/app/headless_example.cc b/chromium/headless/app/headless_example.cc index d344796bdde..693095a151f 100644 --- a/chromium/headless/app/headless_example.cc +++ b/chromium/headless/app/headless_example.cc @@ -97,9 +97,11 @@ void HeadlessExample::DevToolsTargetReady() { void HeadlessExample::OnLoadEventFired( const headless::page::LoadEventFiredParams& params) { // The page has now finished loading. Let's grab a snapshot of the DOM by - // evaluating the outerHTML property on the body element. + // evaluating the innerHTML property on the document element. devtools_client_->GetRuntime()->Evaluate( - "document.body.outerHTML", + "(document.doctype ? new " + "XMLSerializer().serializeToString(document.doctype) + '\\n' : '') + " + "document.documentElement.outerHTML", base::Bind(&HeadlessExample::OnDomFetched, weak_factory_.GetWeakPtr())); } @@ -108,7 +110,7 @@ void HeadlessExample::OnDomFetched( std::string dom; // Make sure the evaluation succeeded before reading the result. if (result->HasExceptionDetails()) { - LOG(ERROR) << "Failed to evaluate document.body.outerHTML: " + LOG(ERROR) << "Failed to serialize document: " << result->GetExceptionDetails()->GetText(); } else if (result->GetResult()->GetValue()->GetAsString(&dom)) { printf("%s\n", dom.c_str()); diff --git a/chromium/headless/app/headless_shell.cc b/chromium/headless/app/headless_shell.cc index b7bb09c26aa..b44101e9a46 100644 --- a/chromium/headless/app/headless_shell.cc +++ b/chromium/headless/app/headless_shell.cc @@ -20,7 +20,9 @@ #include "base/numerics/safe_conversions.h" #include "base/strings/string_number_conversions.h" #include "base/task_scheduler/post_task.h" +#include "build/build_config.h" #include "content/public/app/content_main.h" +#include "content/public/browser/browser_thread.h" #include "content/public/common/content_switches.h" #include "headless/app/headless_shell.h" #include "headless/app/headless_shell_switches.h" @@ -31,6 +33,7 @@ #include "net/base/ip_address.h" #include "net/base/net_errors.h" #include "net/http/http_util.h" +#include "ui/base/ui_base_switches.h" #include "ui/gfx/geometry/size.h" #if defined(OS_WIN) @@ -83,6 +86,11 @@ void HeadlessShell::OnStart(HeadlessBrowser* browser) { browser_->CreateBrowserContextBuilder(); // TODO(eseckler): These switches should also affect BrowserContexts that // are created via DevTools later. + if (base::CommandLine::ForCurrentProcess()->HasSwitch(::switches::kLang)) { + context_builder.SetAcceptLanguage( + base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + ::switches::kLang)); + } DeterministicHttpProtocolHandler* http_handler = nullptr; DeterministicHttpProtocolHandler* https_handler = nullptr; if (base::CommandLine::ForCurrentProcess()->HasSwitch( @@ -153,6 +161,10 @@ void HeadlessShell::Shutdown() { web_contents_->GetDevToolsTarget()->DetachClient(devtools_client_.get()); } } + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDeterministicFetch)) { + devtools_client_->GetNetwork()->GetExperimental()->RemoveObserver(this); + } web_contents_->RemoveObserver(this); web_contents_ = nullptr; browser_context_->Close(); @@ -160,6 +172,7 @@ void HeadlessShell::Shutdown() { } void HeadlessShell::DevToolsTargetReady() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get()); devtools_client_->GetInspector()->GetExperimental()->AddObserver(this); devtools_client_->GetPage()->GetExperimental()->AddObserver(this); @@ -169,10 +182,13 @@ void HeadlessShell::DevToolsTargetReady() { if (base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kDeterministicFetch)) { - devtools_client_->GetPage()->GetExperimental()->SetControlNavigations( - headless::page::SetControlNavigationsParams::Builder() - .SetEnabled(true) - .Build()); + devtools_client_->GetNetwork()->GetExperimental()->AddObserver(this); + devtools_client_->GetNetwork() + ->GetExperimental() + ->SetRequestInterceptionEnabled( + headless::network::SetRequestInterceptionEnabledParams::Builder() + .SetEnabled(true) + .Build()); } if (base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kDefaultBackgroundColor)) { @@ -248,6 +264,7 @@ void HeadlessShell::OnTargetCrashed( } void HeadlessShell::PollReadyState() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); // We need to check the current location in addition to the ready state to // be sure the expected page is ready. devtools_client_->GetRuntime()->Evaluate( @@ -288,11 +305,20 @@ void HeadlessShell::OnLoadEventFired(const page::LoadEventFiredParams& params) { OnPageReady(); } -void HeadlessShell::OnNavigationRequested( - const headless::page::NavigationRequestedParams& params) { - deterministic_dispatcher_->NavigationRequested( - base::MakeUnique<ShellNavigationRequest>(weak_factory_.GetWeakPtr(), - params)); +// network::Observer implementation: +void HeadlessShell::OnRequestIntercepted( + const headless::network::RequestInterceptedParams& params) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (params.GetIsNavigationRequest()) { + deterministic_dispatcher_->NavigationRequested( + base::MakeUnique<ShellNavigationRequest>(weak_factory_.GetWeakPtr(), + params.GetInterceptionId())); + return; + } + devtools_client_->GetNetwork()->GetExperimental()->ContinueInterceptedRequest( + headless::network::ContinueInterceptedRequestParams::Builder() + .SetInterceptionId(params.GetInterceptionId()) + .Build()); } void HeadlessShell::OnPageReady() { @@ -319,15 +345,18 @@ void HeadlessShell::OnPageReady() { } void HeadlessShell::FetchDom() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); devtools_client_->GetRuntime()->Evaluate( - "document.body.outerHTML", + "(document.doctype ? new " + "XMLSerializer().serializeToString(document.doctype) + '\\n' : '') + " + "document.documentElement.outerHTML", base::Bind(&HeadlessShell::OnDomFetched, weak_factory_.GetWeakPtr())); } void HeadlessShell::OnDomFetched( std::unique_ptr<runtime::EvaluateResult> result) { if (result->HasExceptionDetails()) { - LOG(ERROR) << "Failed to evaluate document.body.outerHTML: " + LOG(ERROR) << "Failed to serialize document: " << result->GetExceptionDetails()->GetText(); } else { std::string dom; @@ -339,6 +368,7 @@ void HeadlessShell::OnDomFetched( } void HeadlessShell::InputExpression() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); // Note that a real system should read user input asynchronously, because // otherwise all other browser activity is suspended (e.g., page loading). printf(">>> "); @@ -369,6 +399,7 @@ void HeadlessShell::OnExpressionResult( } void HeadlessShell::CaptureScreenshot() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); devtools_client_->GetPage()->GetExperimental()->CaptureScreenshot( page::CaptureScreenshotParams::Builder().Build(), base::Bind(&HeadlessShell::OnScreenshotCaptured, @@ -387,6 +418,7 @@ void HeadlessShell::OnScreenshotCaptured( } void HeadlessShell::PrintToPDF() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); devtools_client_->GetPage()->GetExperimental()->PrintToPDF( page::PrintToPDFParams::Builder() .SetDisplayHeaderFooter(true) @@ -408,6 +440,7 @@ void HeadlessShell::OnPDFCreated( void HeadlessShell::WriteFile(const std::string& file_path_switch, const std::string& default_file_name, const std::string& base64_data) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); base::FilePath file_name = base::CommandLine::ForCurrentProcess()->GetSwitchValuePath( file_path_switch); @@ -427,6 +460,7 @@ void HeadlessShell::WriteFile(const std::string& file_path_switch, void HeadlessShell::OnFileOpened(const std::string& base64_data, const base::FilePath file_name, base::File::Error error_code) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (!file_proxy_->IsValid()) { LOG(ERROR) << "Writing to file " << file_name.value() << " was unsuccessful, could not open file: " @@ -459,6 +493,7 @@ void HeadlessShell::OnFileWritten(const base::FilePath file_name, const size_t length, base::File::Error error_code, int write_result) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (write_result < static_cast<int>(length)) { // TODO(eseckler): Support recovering from partial writes. LOG(ERROR) << "Writing to file " << file_name.value() @@ -553,7 +588,8 @@ int HeadlessShellMain(HINSTANCE instance, ::switches::kProcessType); if (process_type == crash_reporter::switches::kCrashpadHandler) { return crash_reporter::RunAsCrashpadHandler( - *base::CommandLine::ForCurrentProcess(), ::switches::kProcessType); + *base::CommandLine::ForCurrentProcess(), base::FilePath(), + ::switches::kProcessType, switches::kUserDataDir); } #endif // defined(HEADLESS_USE_CRASPHAD) RunChildProcessIfNeeded(instance, sandbox_info); @@ -568,8 +604,12 @@ int HeadlessShellMain(int argc, const char** argv) { #endif // defined(OS_WIN) HeadlessShell shell; - const base::CommandLine& command_line( - *base::CommandLine::ForCurrentProcess()); +#if defined(OS_FUCHSIA) + // TODO(fuchsia): Remove this when GPU accelerated compositing is ready. + base::CommandLine::ForCurrentProcess()->AppendSwitch(::switches::kDisableGpu); +#endif + + base::CommandLine& command_line(*base::CommandLine::ForCurrentProcess()); if (!ValidateCommandLine(command_line)) return EXIT_FAILURE; @@ -624,6 +664,11 @@ int HeadlessShellMain(int argc, const char** argv) { command_line.GetSwitchValueASCII(switches::kProxyServer); std::unique_ptr<net::ProxyConfig> proxy_config(new net::ProxyConfig); proxy_config->proxy_rules().ParseFromString(proxy_server); + if (command_line.HasSwitch(switches::kProxyBypassList)) { + std::string bypass_list = + command_line.GetSwitchValueASCII(switches::kProxyBypassList); + proxy_config->proxy_rules().bypass_rules.ParseFromString(bypass_list); + } builder.SetProxyConfig(std::move(proxy_config)); } diff --git a/chromium/headless/app/headless_shell.h b/chromium/headless/app/headless_shell.h index 4aef3fd6a38..18092fca723 100644 --- a/chromium/headless/app/headless_shell.h +++ b/chromium/headless/app/headless_shell.h @@ -28,7 +28,8 @@ namespace headless { class HeadlessShell : public HeadlessWebContents::Observer, public emulation::ExperimentalObserver, public inspector::ExperimentalObserver, - public page::ExperimentalObserver { + public page::ExperimentalObserver, + public network::ExperimentalObserver { public: HeadlessShell(); ~HeadlessShell() override; @@ -50,8 +51,10 @@ class HeadlessShell : public HeadlessWebContents::Observer, // page::Observer implementation: void OnLoadEventFired(const page::LoadEventFiredParams& params) override; - void OnNavigationRequested( - const headless::page::NavigationRequestedParams& params) override; + + // network::Observer implementation: + void OnRequestIntercepted( + const headless::network::RequestInterceptedParams& params) override; virtual void Shutdown(); diff --git a/chromium/headless/app/headless_shell_switches.cc b/chromium/headless/app/headless_shell_switches.cc index 7293dd9a53e..dc23d6d715a 100644 --- a/chromium/headless/app/headless_shell_switches.cc +++ b/chromium/headless/app/headless_shell_switches.cc @@ -32,6 +32,12 @@ const char kHideScrollbars[] = "hide-scrollbars"; // Save a pdf file of the loaded page. const char kPrintToPDF[] = "print-to-pdf"; +// Specifies a list of hosts for whom we bypass proxy settings and use direct +// connections. Ignored unless --proxy-server is also specified. This is a +// comma-separated list of bypass rules. See: "net/proxy/proxy_bypass_rules.h" +// for the format of these rules. +const char kProxyBypassList[] = "proxy-bypass-list"; + // Uses a specified proxy server, overrides system settings. This switch only // affects HTTP and HTTPS requests. const char kProxyServer[] = "proxy-server"; diff --git a/chromium/headless/app/headless_shell_switches.h b/chromium/headless/app/headless_shell_switches.h index 90566c6f085..b18895d0652 100644 --- a/chromium/headless/app/headless_shell_switches.h +++ b/chromium/headless/app/headless_shell_switches.h @@ -17,6 +17,7 @@ extern const char kDeterministicFetch[]; extern const char kDumpDom[]; extern const char kHideScrollbars[]; extern const char kPrintToPDF[]; +extern const char kProxyBypassList[]; extern const char kProxyServer[]; extern const char kRemoteDebuggingAddress[]; extern const char kRemoteDebuggingSocketFd[]; diff --git a/chromium/headless/app/shell_navigation_request.cc b/chromium/headless/app/shell_navigation_request.cc index 3c419bce8ec..64d5840168c 100644 --- a/chromium/headless/app/shell_navigation_request.cc +++ b/chromium/headless/app/shell_navigation_request.cc @@ -4,39 +4,73 @@ #include "headless/app/shell_navigation_request.h" +#include "content/public/browser/browser_thread.h" #include "headless/app/headless_shell.h" namespace headless { ShellNavigationRequest::ShellNavigationRequest( base::WeakPtr<HeadlessShell> headless_shell, - const page::NavigationRequestedParams& params) - : headless_shell_(headless_shell), - navigation_id_(params.GetNavigationId()) {} + const std::string& interception_id) + : headless_shell_( + base::MakeUnique<base::WeakPtr<HeadlessShell>>(headless_shell)), + interception_id_(interception_id) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +} ShellNavigationRequest::~ShellNavigationRequest() {} void ShellNavigationRequest::StartProcessing(base::Closure done_callback) { - if (!headless_shell_) + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + // The devtools bindings can only be called on the UI thread. + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&ShellNavigationRequest::StartProcessingOnUiThread, + base::Passed(std::move(headless_shell_)), interception_id_, + std::move(done_callback))); +} + +// static +void ShellNavigationRequest::StartProcessingOnUiThread( + std::unique_ptr<base::WeakPtr<HeadlessShell>> headless_shell, + std::string interception_id, + base::Closure done_callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (!headless_shell) return; // Allow the navigation to proceed. - headless_shell_->devtools_client() - ->GetPage() + (*headless_shell) + ->devtools_client() + ->GetNetwork() ->GetExperimental() - ->ProcessNavigation( - headless::page::ProcessNavigationParams::Builder() - .SetNavigationId(navigation_id_) - .SetResponse(headless::page::NavigationResponse::PROCEED) + ->ContinueInterceptedRequest( + headless::network::ContinueInterceptedRequestParams::Builder() + .SetInterceptionId(interception_id) .Build(), - base::Bind(&ShellNavigationRequest::ProcessNavigationResult, - done_callback)); + base::Bind(&ShellNavigationRequest::ContinueInterceptedRequestResult, + std::move(done_callback))); } // static -void ShellNavigationRequest::ProcessNavigationResult( +void ShellNavigationRequest::ContinueInterceptedRequestResult( base::Closure done_callback, - std::unique_ptr<page::ProcessNavigationResult>) { + std::unique_ptr<network::ContinueInterceptedRequestResult>) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + // The |done_callback| must be fired on the IO thread. + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind( + &ShellNavigationRequest::ContinueInterceptedRequestResultOnIoThread, + std::move(done_callback))); +} + +// static +void ShellNavigationRequest::ContinueInterceptedRequestResultOnIoThread( + base::Closure done_callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); done_callback.Run(); } diff --git a/chromium/headless/app/shell_navigation_request.h b/chromium/headless/app/shell_navigation_request.h index 20ef77f0e4e..1abad6ed305 100644 --- a/chromium/headless/app/shell_navigation_request.h +++ b/chromium/headless/app/shell_navigation_request.h @@ -8,7 +8,7 @@ #include "base/callback.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" -#include "headless/public/devtools/domains/page.h" +#include "headless/public/devtools/domains/network.h" #include "headless/public/util/navigation_request.h" namespace headless { @@ -19,21 +19,32 @@ class HeadlessShell; class ShellNavigationRequest : public NavigationRequest { public: ShellNavigationRequest(base::WeakPtr<HeadlessShell> headless_shell, - const page::NavigationRequestedParams& params); + const std::string& interception_id); ~ShellNavigationRequest() override; void StartProcessing(base::Closure done_callback) override; private: + static void StartProcessingOnUiThread( + std::unique_ptr<base::WeakPtr<HeadlessShell>> headless_shell, + std::string interception_id, + base::Closure done_callback); + // Note the navigation likely isn't done when this is called, however we // expect it will have been committed and the initial resource load requested. - static void ProcessNavigationResult( + static void ContinueInterceptedRequestResult( base::Closure done_callback, - std::unique_ptr<page::ProcessNavigationResult>); + std::unique_ptr<network::ContinueInterceptedRequestResult>); + + static void ContinueInterceptedRequestResultOnIoThread( + base::Closure done_callback); - base::WeakPtr<HeadlessShell> headless_shell_; - int navigation_id_; + // Yuck we need to post a weak pointer from the IO -> UI threads but WeakPtr + // is super finicky about which threads it's touched on. By boxing this up in + // a unique_ptr we can pass it about and only touch it on the UI thread. + std::unique_ptr<base::WeakPtr<HeadlessShell>> headless_shell_; + std::string interception_id_; }; } // namespace headless diff --git a/chromium/headless/lib/browser/OWNERS b/chromium/headless/lib/browser/OWNERS new file mode 100644 index 00000000000..d2386d842bd --- /dev/null +++ b/chromium/headless/lib/browser/OWNERS @@ -0,0 +1,4 @@ +per-file headless_browser_manifest_overlay.json=set noparent +per-file headless_browser_manifest_overlay.json=file://ipc/SECURITY_OWNERS +per-file headless_packaged_services_manifest_overlay.json=set noparent +per-file headless_packaged_services_manifest_overlay.json=file://ipc/SECURITY_OWNERS diff --git a/chromium/headless/lib/browser/headless_browser_context_impl.cc b/chromium/headless/lib/browser/headless_browser_context_impl.cc index 1954451f3ff..bdcaad176e1 100644 --- a/chromium/headless/lib/browser/headless_browser_context_impl.cc +++ b/chromium/headless/lib/browser/headless_browser_context_impl.cc @@ -92,6 +92,13 @@ HeadlessBrowserContextImpl::HeadlessBrowserContextImpl( HeadlessBrowserContextImpl::~HeadlessBrowserContextImpl() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + // Inform observers that we're going away. + { + base::AutoLock lock(observers_lock_); + for (auto& observer : observers_) + observer.OnHeadlessBrowserContextDestruct(); + } + // Destroy all web contents before shutting down storage partitions. web_contents_map_.clear(); @@ -168,6 +175,19 @@ int HeadlessBrowserContextImpl::GetFrameTreeNodeId(int render_process_id, return find_it->second; } +int HeadlessBrowserContextImpl::GetFrameTreeNodeIdForDevToolsFrameId( + const std::string& devtools_id) const { + base::AutoLock lock(frame_tree_node_map_lock_); + for (const auto& pair : frame_tree_node_map_) { + std::string frame_devtools_id = content::DevToolsAgentHost:: + GetUntrustedDevToolsFrameIdForFrameTreeNodeId(pair.first.first, + pair.second); + if (frame_devtools_id == devtools_id) + return pair.second; + } + return -1; +} + void HeadlessBrowserContextImpl::Close() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); browser_->DestroyBrowserContext(this); @@ -380,6 +400,19 @@ HeadlessBrowserContext::Builder::SetProductNameAndVersion( return *this; } +HeadlessBrowserContext::Builder& HeadlessBrowserContext::Builder::SetUserAgent( + const std::string& user_agent) { + options_->user_agent_ = user_agent; + return *this; +} + +HeadlessBrowserContext::Builder& +HeadlessBrowserContext::Builder::SetAcceptLanguage( + const std::string& accept_language) { + options_->accept_language_ = accept_language; + return *this; +} + HeadlessBrowserContext::Builder& HeadlessBrowserContext::Builder::SetProxyConfig( std::unique_ptr<net::ProxyConfig> proxy_config) { diff --git a/chromium/headless/lib/browser/headless_browser_context_impl.h b/chromium/headless/lib/browser/headless_browser_context_impl.h index ca4bac35ac7..98b6ccdffdd 100644 --- a/chromium/headless/lib/browser/headless_browser_context_impl.h +++ b/chromium/headless/lib/browser/headless_browser_context_impl.h @@ -95,6 +95,11 @@ class HeadlessBrowserContextImpl : public HeadlessBrowserContext, // if it can't be found. Can be called on any thread. int GetFrameTreeNodeId(int render_process_id, int render_frame_id) const; + // Returns the FrameTreeNode id corresponding to |devtools_id| or -1 if it + // can't be found. Must be called on the IO thread. + int GetFrameTreeNodeIdForDevToolsFrameId( + const std::string& devtools_id) const; + void NotifyChildContentsCreated(HeadlessWebContentsImpl* parent, HeadlessWebContentsImpl* child); diff --git a/chromium/headless/lib/browser/headless_browser_context_options.cc b/chromium/headless/lib/browser/headless_browser_context_options.cc index 5ca1ab3a2d6..5898576830a 100644 --- a/chromium/headless/lib/browser/headless_browser_context_options.cc +++ b/chromium/headless/lib/browser/headless_browser_context_options.cc @@ -41,8 +41,13 @@ const std::string& HeadlessBrowserContextOptions::product_name_and_version() browser_options_->product_name_and_version); } +const std::string& HeadlessBrowserContextOptions::accept_language() const { + return ReturnOverriddenValue(accept_language_, + browser_options_->accept_language); +} + const std::string& HeadlessBrowserContextOptions::user_agent() const { - return browser_options_->user_agent; + return ReturnOverriddenValue(user_agent_, browser_options_->user_agent); } const net::ProxyConfig* HeadlessBrowserContextOptions::proxy_config() const { diff --git a/chromium/headless/lib/browser/headless_browser_context_options.h b/chromium/headless/lib/browser/headless_browser_context_options.h index 1d3fb84b038..23a4f6251a7 100644 --- a/chromium/headless/lib/browser/headless_browser_context_options.h +++ b/chromium/headless/lib/browser/headless_browser_context_options.h @@ -27,6 +27,7 @@ class HeadlessBrowserContextOptions { HeadlessBrowserContextOptions&& options); const std::string& product_name_and_version() const; + const std::string& accept_language() const; const std::string& user_agent() const; // See HeadlessBrowser::Options::proxy_config. @@ -62,6 +63,8 @@ class HeadlessBrowserContextOptions { HeadlessBrowser::Options* browser_options_; base::Optional<std::string> product_name_and_version_; + base::Optional<std::string> accept_language_; + base::Optional<std::string> user_agent_; std::unique_ptr<net::ProxyConfig> proxy_config_; base::Optional<std::string> host_resolver_rules_; base::Optional<gfx::Size> window_size_; diff --git a/chromium/headless/lib/browser/headless_browser_impl.cc b/chromium/headless/lib/browser/headless_browser_impl.cc index 0f64f11b922..08ba287a98e 100644 --- a/chromium/headless/lib/browser/headless_browser_impl.cc +++ b/chromium/headless/lib/browser/headless_browser_impl.cc @@ -30,6 +30,10 @@ #include "ui/events/devices/device_data_manager.h" #include "ui/gfx/geometry/size.h" +#if defined(USE_NSS_CERTS) +#include "net/cert_net/nss_ocsp.h" +#endif + namespace content { class DevToolsAgentHost; } @@ -131,6 +135,11 @@ void HeadlessBrowserImpl::set_browser_main_parts( } void HeadlessBrowserImpl::RunOnStartCallback() { +#if defined(USE_NSS_CERTS) + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&net::SetMessageLoopForNSSHttpIO)); +#endif // We don't support the tethering domain on this agent host. agent_host_ = content::DevToolsAgentHost::CreateForBrowser( nullptr, content::DevToolsAgentHost::CreateServerSocketCallback()); diff --git a/chromium/headless/lib/browser/headless_browser_impl_mac.mm b/chromium/headless/lib/browser/headless_browser_impl_mac.mm index 9c40982dc33..9b3be46db03 100644 --- a/chromium/headless/lib/browser/headless_browser_impl_mac.mm +++ b/chromium/headless/lib/browser/headless_browser_impl_mac.mm @@ -10,9 +10,24 @@ namespace headless { +namespace { + +NSString* const kActivityReason = @"Batch headless process"; +const NSActivityOptions kActivityOptions = + (NSActivityUserInitiatedAllowingIdleSystemSleep | + NSActivityLatencyCritical) & + ~(NSActivitySuddenTerminationDisabled | + NSActivityAutomaticTerminationDisabled); + +} // namespace + void HeadlessBrowserImpl::PlatformInitialize() {} -void HeadlessBrowserImpl::PlatformStart() {} +void HeadlessBrowserImpl::PlatformStart() { + // Disallow headless to be throttled as a background process. + [[NSProcessInfo processInfo] beginActivityWithOptions:kActivityOptions + reason:kActivityReason]; +} void HeadlessBrowserImpl::PlatformInitializeWebContents( HeadlessWebContentsImpl* web_contents) { diff --git a/chromium/headless/lib/browser/headless_browser_main_parts_mac.mm b/chromium/headless/lib/browser/headless_browser_main_parts_mac.mm index e8ab0a3e0f0..a1404e78306 100644 --- a/chromium/headless/lib/browser/headless_browser_main_parts_mac.mm +++ b/chromium/headless/lib/browser/headless_browser_main_parts_mac.mm @@ -13,6 +13,8 @@ namespace headless { void HeadlessBrowserMainParts::PreMainMessageLoopStart() { // Force the NSApplication subclass to be used. [HeadlessShellCrApplication sharedApplication]; + // Force hide dock and menu bar. + [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory]; } } // namespace headless diff --git a/chromium/headless/lib/browser/headless_browser_manifest_overlay_template.json b/chromium/headless/lib/browser/headless_browser_manifest_overlay.json index d5e3e9943c0..6483a30250d 100644 --- a/chromium/headless/lib/browser/headless_browser_manifest_overlay_template.json +++ b/chromium/headless/lib/browser/headless_browser_manifest_overlay.json @@ -1,6 +1,11 @@ { "name": "content_browser", "interface_provider_specs": { + "service_manager:connector": { + "requires": { + "pdf_compositor": [ "composite" ] + } + }, "navigation:frame": { "provides": { "renderer": [] diff --git a/chromium/headless/lib/browser/headless_content_browser_client.cc b/chromium/headless/lib/browser/headless_content_browser_client.cc index bce11ceb850..2915b27be4f 100644 --- a/chromium/headless/lib/browser/headless_content_browser_client.cc +++ b/chromium/headless/lib/browser/headless_content_browser_client.cc @@ -28,8 +28,10 @@ #include "headless/lib/browser/headless_devtools_manager_delegate.h" #include "headless/lib/browser/headless_quota_permission_context.h" #include "headless/lib/headless_macros.h" +#include "net/base/url_util.h" #include "storage/browser/quota/quota_settings.h" #include "ui/base/resource/resource_bundle.h" +#include "ui/base/ui_base_switches.h" #include "ui/gfx/switches.h" #if defined(HEADLESS_USE_BREAKPAD) @@ -39,6 +41,11 @@ #include "content/public/common/content_descriptors.h" #endif // defined(HEADLESS_USE_BREAKPAD) +#if BUILDFLAG(ENABLE_BASIC_PRINTING) && !defined(CHROME_MULTIPLE_DLL_CHILD) +#include "base/strings/utf_string_conversions.h" +#include "components/printing/service/public/interfaces/pdf_compositor.mojom.h" +#endif + namespace headless { namespace { @@ -140,20 +147,28 @@ HeadlessContentBrowserClient::GetServiceManifestOverlay( base::StringPiece name) { if (name == content::mojom::kBrowserServiceName) return GetBrowserServiceManifestOverlay(); - else if (name == content::mojom::kRendererServiceName) + if (name == content::mojom::kRendererServiceName) return GetRendererServiceManifestOverlay(); + if (name == content::mojom::kPackagedServicesServiceName) + return GetPackagedServicesServiceManifestOverlay(); return nullptr; } +void HeadlessContentBrowserClient::RegisterOutOfProcessServices( + OutOfProcessServiceMap* services) { +#if BUILDFLAG(ENABLE_BASIC_PRINTING) && !defined(CHROME_MULTIPLE_DLL_CHILD) + (*services)[printing::mojom::kServiceName] = { + base::ASCIIToUTF16("PDF Compositor Service"), + content::SANDBOX_TYPE_UTILITY}; +#endif +} + std::unique_ptr<base::Value> HeadlessContentBrowserClient::GetBrowserServiceManifestOverlay() { - if (browser_->options()->mojo_service_names.empty()) - return nullptr; - base::StringPiece manifest_template = ui::ResourceBundle::GetSharedInstance().GetRawDataResource( - IDR_HEADLESS_BROWSER_MANIFEST_OVERLAY_TEMPLATE); + IDR_HEADLESS_BROWSER_MANIFEST_OVERLAY); std::unique_ptr<base::Value> manifest = base::JSONReader::Read(manifest_template); @@ -179,6 +194,14 @@ HeadlessContentBrowserClient::GetRendererServiceManifestOverlay() { return base::JSONReader::Read(manifest_template); } +std::unique_ptr<base::Value> +HeadlessContentBrowserClient::GetPackagedServicesServiceManifestOverlay() { + base::StringPiece manifest_template = + ui::ResourceBundle::GetSharedInstance().GetRawDataResource( + IDR_HEADLESS_PACKAGED_SERVICES_MANIFEST_OVERLAY); + return base::JSONReader::Read(manifest_template); +} + content::QuotaPermissionContext* HeadlessContentBrowserClient::CreateQuotaPermissionContext() { return new HeadlessQuotaPermissionContext(); @@ -196,7 +219,7 @@ void HeadlessContentBrowserClient::GetQuotaSettings( void HeadlessContentBrowserClient::GetAdditionalMappedFilesForChildProcess( const base::CommandLine& command_line, int child_process_id, - content::FileDescriptorInfo* mappings) { + content::PosixFileDescriptorInfo* mappings) { #if defined(HEADLESS_USE_BREAKPAD) int crash_signal_fd = GetCrashSignalFD(command_line, *browser_->options()); if (crash_signal_fd >= 0) @@ -221,6 +244,25 @@ void HeadlessContentBrowserClient::AppendExtraCommandLineSwitches( if (breakpad::IsCrashReporterEnabled()) command_line->AppendSwitch(::switches::kEnableCrashReporter); #endif // defined(HEADLESS_USE_BREAKPAD) + + // If we're spawning a renderer, then override the language switch. + if (command_line->GetSwitchValueASCII(::switches::kProcessType) == + ::switches::kRendererProcess) { + content::RenderProcessHost* render_process_host = + content::RenderProcessHost::FromID(child_process_id); + if (render_process_host) { + HeadlessBrowserContextImpl* headless_browser_context_impl = + HeadlessBrowserContextImpl::From( + render_process_host->GetBrowserContext()); + std::vector<base::StringPiece> languages = base::SplitStringPiece( + headless_browser_context_impl->options()->accept_language(), ",", + base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + if (!languages.empty()) { + command_line->AppendSwitchASCII(::switches::kLang, + languages[0].as_string()); + } + } + } } void HeadlessContentBrowserClient::AllowCertificateError( @@ -234,8 +276,18 @@ void HeadlessContentBrowserClient::AllowCertificateError( bool expired_previous_decision, const base::Callback<void(content::CertificateRequestResultType)>& callback) { - if (!callback.is_null()) + if (!callback.is_null()) { + // If --allow-insecure-localhost is specified, and the request + // was for localhost, then the error was not fatal. + bool allow_localhost = base::CommandLine::ForCurrentProcess()->HasSwitch( + ::switches::kAllowInsecureLocalhost); + if (allow_localhost && net::IsLocalhost(request_url.host())) { + callback.Run(content::CERTIFICATE_REQUEST_RESULT_TYPE_CONTINUE); + return; + } + callback.Run(content::CERTIFICATE_REQUEST_RESULT_TYPE_DENY); + } } void HeadlessContentBrowserClient::ResourceDispatcherHostCreated() { diff --git a/chromium/headless/lib/browser/headless_content_browser_client.h b/chromium/headless/lib/browser/headless_content_browser_client.h index 5efc35393ad..f018a500120 100644 --- a/chromium/headless/lib/browser/headless_content_browser_client.h +++ b/chromium/headless/lib/browser/headless_content_browser_client.h @@ -25,6 +25,7 @@ class HeadlessContentBrowserClient : public content::ContentBrowserClient { content::DevToolsManagerDelegate* GetDevToolsManagerDelegate() override; std::unique_ptr<base::Value> GetServiceManifestOverlay( base::StringPiece name) override; + void RegisterOutOfProcessServices(OutOfProcessServiceMap* services) override; content::QuotaPermissionContext* CreateQuotaPermissionContext() override; void GetQuotaSettings( content::BrowserContext* context, @@ -34,7 +35,7 @@ class HeadlessContentBrowserClient : public content::ContentBrowserClient { void GetAdditionalMappedFilesForChildProcess( const base::CommandLine& command_line, int child_process_id, - content::FileDescriptorInfo* mappings) override; + content::PosixFileDescriptorInfo* mappings) override; #endif void AppendExtraCommandLineSwitches(base::CommandLine* command_line, int child_process_id) override; @@ -58,6 +59,7 @@ class HeadlessContentBrowserClient : public content::ContentBrowserClient { private: std::unique_ptr<base::Value> GetBrowserServiceManifestOverlay(); std::unique_ptr<base::Value> GetRendererServiceManifestOverlay(); + std::unique_ptr<base::Value> GetPackagedServicesServiceManifestOverlay(); HeadlessBrowserImpl* browser_; // Not owned. diff --git a/chromium/headless/lib/browser/headless_devtools.cc b/chromium/headless/lib/browser/headless_devtools.cc index d69b55768cf..21e498de64e 100644 --- a/chromium/headless/lib/browser/headless_devtools.cc +++ b/chromium/headless/lib/browser/headless_devtools.cc @@ -119,7 +119,7 @@ void StartLocalDevToolsHttpHandler(HeadlessBrowser::Options* options) { content::DevToolsAgentHost::StartRemoteDebuggingServer( std::move(socket_factory), std::string(), options->user_data_dir, // TODO(altimin): Figure a proper value for this. - base::FilePath(), options->product_name_and_version, options->user_agent); + base::FilePath()); } void StopLocalDevToolsHttpHandler() { diff --git a/chromium/headless/lib/browser/headless_devtools_client_impl.cc b/chromium/headless/lib/browser/headless_devtools_client_impl.cc index 3d07de1a574..ec7e3d4aa01 100644 --- a/chromium/headless/lib/browser/headless_devtools_client_impl.cc +++ b/chromium/headless/lib/browser/headless_devtools_client_impl.cc @@ -75,11 +75,11 @@ HeadlessDevToolsClientImpl::~HeadlessDevToolsClientImpl() {} bool HeadlessDevToolsClientImpl::AttachToHost( content::DevToolsAgentHost* agent_host) { DCHECK(!agent_host_); - if (agent_host->AttachClient(this)) { - agent_host_ = agent_host; - return true; - } - return false; + if (agent_host->IsAttached()) + return false; + agent_host->AttachClient(this); + agent_host_ = agent_host; + return true; } void HeadlessDevToolsClientImpl::ForceAttachToHost( diff --git a/chromium/headless/lib/browser/headless_network_delegate.cc b/chromium/headless/lib/browser/headless_network_delegate.cc index d6722a626d7..cad704764e2 100644 --- a/chromium/headless/lib/browser/headless_network_delegate.cc +++ b/chromium/headless/lib/browser/headless_network_delegate.cc @@ -4,6 +4,7 @@ #include "headless/lib/browser/headless_network_delegate.h" +#include "content/public/browser/resource_request_info.h" #include "headless/lib/browser/headless_browser_context_impl.h" #include "net/base/net_errors.h" #include "net/http/http_request_headers.h" @@ -21,9 +22,17 @@ const char kDevToolsEmulateNetworkConditionsClientId[] = HeadlessNetworkDelegate::HeadlessNetworkDelegate( HeadlessBrowserContextImpl* headless_browser_context) - : headless_browser_context_(headless_browser_context) {} + : headless_browser_context_(headless_browser_context) { + base::AutoLock lock(lock_); + if (headless_browser_context_) + headless_browser_context_->AddObserver(this); +} -HeadlessNetworkDelegate::~HeadlessNetworkDelegate() {} +HeadlessNetworkDelegate::~HeadlessNetworkDelegate() { + base::AutoLock lock(lock_); + if (headless_browser_context_) + headless_browser_context_->RemoveObserver(this); +} int HeadlessNetworkDelegate::OnBeforeURLRequest( net::URLRequest* request, @@ -62,8 +71,38 @@ void HeadlessNetworkDelegate::OnResponseStarted(net::URLRequest* request, void HeadlessNetworkDelegate::OnCompleted(net::URLRequest* request, bool started, int net_error) { - if (net_error != net::OK) + base::AutoLock lock(lock_); + if (!headless_browser_context_) + return; + + if (net_error != net::OK) { headless_browser_context_->NotifyUrlRequestFailed(request, net_error); + return; + } + + if (request->response_info().network_accessed || request->was_cached() || + request->GetResponseCode() != 200 || request->GetRawBodyBytes() > 0) { + return; + } + + const content::ResourceRequestInfo* resource_request_info = + content::ResourceRequestInfo::ForRequest(request); + if (!resource_request_info) + return; + + content::ResourceType resource_type = + resource_request_info->GetResourceType(); + + if (resource_type != content::RESOURCE_TYPE_MAIN_FRAME && + resource_type != content::RESOURCE_TYPE_SUB_FRAME) { + return; + } + + // The |request| was for a navigation where an empty response was served but + // the network wasn't accessed and it wasn't cached, so we can be reasonably + // certain that DevTools canceled the associated navigation. When it does so + // an empty resource is served. + headless_browser_context_->NotifyUrlRequestFailed(request, net::ERR_ABORTED); } void HeadlessNetworkDelegate::OnURLRequestDestroyed(net::URLRequest* request) {} @@ -98,4 +137,9 @@ bool HeadlessNetworkDelegate::OnCanAccessFile( return true; } +void HeadlessNetworkDelegate::OnHeadlessBrowserContextDestruct() { + base::AutoLock lock(lock_); + headless_browser_context_ = nullptr; +} + } // namespace headless diff --git a/chromium/headless/lib/browser/headless_network_delegate.h b/chromium/headless/lib/browser/headless_network_delegate.h index fc11b76b530..ccfb5f63976 100644 --- a/chromium/headless/lib/browser/headless_network_delegate.h +++ b/chromium/headless/lib/browser/headless_network_delegate.h @@ -7,6 +7,8 @@ #include "base/macros.h" +#include "base/synchronization/lock.h" +#include "headless/public/headless_browser_context.h" #include "net/base/network_delegate_impl.h" namespace headless { @@ -14,13 +16,15 @@ class HeadlessBrowserContextImpl; // We use the HeadlessNetworkDelegate to remove DevTools request headers before // requests are actually fetched and for reporting failed network requests. -class HeadlessNetworkDelegate : public net::NetworkDelegateImpl { +class HeadlessNetworkDelegate : public net::NetworkDelegateImpl, + public HeadlessBrowserContext::Observer { public: explicit HeadlessNetworkDelegate( HeadlessBrowserContextImpl* headless_browser_context); ~HeadlessNetworkDelegate() override; private: + // net::NetworkDelegateImpl implementation: int OnBeforeURLRequest(net::URLRequest* request, const net::CompletionCallback& callback, GURL* new_url) override; @@ -69,6 +73,10 @@ class HeadlessNetworkDelegate : public net::NetworkDelegateImpl { const base::FilePath& original_path, const base::FilePath& absolute_path) const override; + // HeadlessBrowserContext::Observer implementation: + void OnHeadlessBrowserContextDestruct() override; + + base::Lock lock_; // Protects |headless_browser_context_|. HeadlessBrowserContextImpl* headless_browser_context_; // Not owned. DISALLOW_COPY_AND_ASSIGN(HeadlessNetworkDelegate); diff --git a/chromium/headless/lib/browser/headless_packaged_services_manifest_overlay.json b/chromium/headless/lib/browser/headless_packaged_services_manifest_overlay.json new file mode 100644 index 00000000000..2b560d9751c --- /dev/null +++ b/chromium/headless/lib/browser/headless_packaged_services_manifest_overlay.json @@ -0,0 +1,4 @@ +{ + "name": "content_packaged_services", + "interface_provider_specs": {} +} diff --git a/chromium/headless/lib/browser/headless_print_manager.cc b/chromium/headless/lib/browser/headless_print_manager.cc index 2c761cd84fb..327937fd518 100644 --- a/chromium/headless/lib/browser/headless_print_manager.cc +++ b/chromium/headless/lib/browser/headless_print_manager.cc @@ -293,7 +293,7 @@ void HeadlessPrintManager::OnDidPrintPage( return; } auto metafile = base::MakeUnique<printing::PdfMetafileSkia>( - printing::PDF_SKIA_DOCUMENT_TYPE); + printing::SkiaDocumentType::PDF); if (!metafile->InitFromData(shared_buf->memory(), params.data_size)) { ReleaseJob(METAFILE_INVALID_HEADER); return; 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 3b1d2db63bd..a2dbb0d67bb 100644 --- a/chromium/headless/lib/browser/headless_url_request_context_getter.cc +++ b/chromium/headless/lib/browser/headless_url_request_context_getter.cc @@ -11,9 +11,11 @@ #include "base/memory/ptr_util.h" #include "base/task_scheduler/post_task.h" #include "content/public/browser/browser_thread.h" +#include "headless/lib/browser/headless_browser_context_impl.h" #include "headless/lib/browser/headless_browser_context_options.h" #include "headless/lib/browser/headless_network_delegate.h" #include "net/dns/mapped_host_resolver.h" +#include "net/http/http_util.h" #include "net/proxy/proxy_service.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_builder.h" @@ -29,6 +31,7 @@ HeadlessURLRequestContextGetter::HeadlessURLRequestContextGetter( net::NetLog* net_log, HeadlessBrowserContextImpl* headless_browser_context) : io_task_runner_(std::move(io_task_runner)), + accept_language_(options->accept_language()), user_agent_(options->user_agent()), host_resolver_rules_(options->host_resolver_rules()), proxy_config_(options->proxy_config()), @@ -54,16 +57,23 @@ HeadlessURLRequestContextGetter::HeadlessURLRequestContextGetter( proxy_config_service_ = net::ProxyService::CreateSystemProxyConfigService(io_task_runner_); } + base::AutoLock lock(lock_); + headless_browser_context_->AddObserver(this); } -HeadlessURLRequestContextGetter::~HeadlessURLRequestContextGetter() {} +HeadlessURLRequestContextGetter::~HeadlessURLRequestContextGetter() { + base::AutoLock lock(lock_); + if (headless_browser_context_) + headless_browser_context_->RemoveObserver(this); +} net::URLRequestContext* HeadlessURLRequestContextGetter::GetURLRequestContext() { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); if (!url_request_context_) { net::URLRequestContextBuilder builder; - // TODO(skyostil): Make language settings configurable. + builder.set_accept_language( + net::HttpUtil::GenerateAcceptLanguageHeader(accept_language_)); builder.set_user_agent(user_agent_); // TODO(skyostil): Make these configurable. builder.set_data_enabled(true); @@ -73,8 +83,12 @@ HeadlessURLRequestContextGetter::GetURLRequestContext() { } else { builder.set_proxy_config_service(std::move(proxy_config_service_)); } - builder.set_network_delegate( - base::MakeUnique<HeadlessNetworkDelegate>(headless_browser_context_)); + + { + base::AutoLock lock(lock_); + builder.set_network_delegate( + base::MakeUnique<HeadlessNetworkDelegate>(headless_browser_context_)); + } if (!host_resolver_rules_.empty()) { std::unique_ptr<net::HostResolver> host_resolver( @@ -108,4 +122,9 @@ net::HostResolver* HeadlessURLRequestContextGetter::host_resolver() const { return url_request_context_->host_resolver(); } +void HeadlessURLRequestContextGetter::OnHeadlessBrowserContextDestruct() { + base::AutoLock lock(lock_); + headless_browser_context_ = nullptr; +} + } // namespace headless 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 5ee7c7fc36a..92341b3b497 100644 --- a/chromium/headless/lib/browser/headless_url_request_context_getter.h +++ b/chromium/headless/lib/browser/headless_url_request_context_getter.h @@ -30,7 +30,9 @@ namespace headless { class HeadlessBrowserContextOptions; class HeadlessBrowserContextImpl; -class HeadlessURLRequestContextGetter : public net::URLRequestContextGetter { +class HeadlessURLRequestContextGetter + : public net::URLRequestContextGetter, + public HeadlessBrowserContext::Observer { public: HeadlessURLRequestContextGetter( scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, @@ -48,6 +50,9 @@ class HeadlessURLRequestContextGetter : public net::URLRequestContextGetter { net::HostResolver* host_resolver() const; + // HeadlessBrowserContext::Observer implementation: + void OnHeadlessBrowserContextDestruct() override; + protected: ~HeadlessURLRequestContextGetter() override; @@ -57,6 +62,7 @@ class HeadlessURLRequestContextGetter : public net::URLRequestContextGetter { // The |options| object given to the constructor is not guaranteed to outlive // this class, so we make copies of the parts we need to access on the IO // thread. + std::string accept_language_; std::string user_agent_; std::string host_resolver_rules_; const net::ProxyConfig* proxy_config_; // Not owned. @@ -65,7 +71,9 @@ class HeadlessURLRequestContextGetter : public net::URLRequestContextGetter { std::unique_ptr<net::URLRequestContext> url_request_context_; content::ProtocolHandlerMap protocol_handlers_; content::URLRequestInterceptorScopedVector request_interceptors_; - net::NetLog* net_log_; // Not owned. + net::NetLog* net_log_; // Not owned + + base::Lock lock_; // Protects |headless_browser_context_|. HeadlessBrowserContextImpl* headless_browser_context_; // Not owned. DISALLOW_COPY_AND_ASSIGN(HeadlessURLRequestContextGetter); diff --git a/chromium/headless/lib/browser/headless_web_contents_impl.cc b/chromium/headless/lib/browser/headless_web_contents_impl.cc index b5bfb40b137..82474bc2c48 100644 --- a/chromium/headless/lib/browser/headless_web_contents_impl.cc +++ b/chromium/headless/lib/browser/headless_web_contents_impl.cc @@ -27,6 +27,7 @@ #include "content/public/browser/web_contents_delegate.h" #include "content/public/common/bindings_policy.h" #include "content/public/common/origin_util.h" +#include "content/public/common/renderer_preferences.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" @@ -124,6 +125,63 @@ class HeadlessWebContentsImpl::Delegate : public content::WebContentsDelegate { headless_contents->SetBounds(rect); } + content::WebContents* OpenURLFromTab( + content::WebContents* source, + const content::OpenURLParams& params) override { + DCHECK_EQ(source, headless_web_contents_->web_contents()); + content::WebContents* target = nullptr; + switch (params.disposition) { + case WindowOpenDisposition::CURRENT_TAB: + target = source; + break; + + case WindowOpenDisposition::NEW_POPUP: + case WindowOpenDisposition::NEW_WINDOW: + case WindowOpenDisposition::NEW_BACKGROUND_TAB: + case WindowOpenDisposition::NEW_FOREGROUND_TAB: { + HeadlessWebContentsImpl* child_contents = HeadlessWebContentsImpl::From( + headless_web_contents_->browser_context() + ->CreateWebContentsBuilder() + .SetAllowTabSockets( + !!headless_web_contents_->GetHeadlessTabSocket()) + .SetWindowSize(source->GetContainerBounds().size()) + .Build()); + headless_web_contents_->browser_context()->NotifyChildContentsCreated( + headless_web_contents_, child_contents); + target = child_contents->web_contents(); + break; + } + + // TODO(veluca): add support for other disposition types. + case WindowOpenDisposition::SINGLETON_TAB: + case WindowOpenDisposition::OFF_THE_RECORD: + case WindowOpenDisposition::SAVE_TO_DISK: + case WindowOpenDisposition::IGNORE_ACTION: + default: + return nullptr; + } + + content::NavigationController::LoadURLParams load_url_params(params.url); + load_url_params.source_site_instance = params.source_site_instance; + load_url_params.transition_type = params.transition; + load_url_params.frame_tree_node_id = params.frame_tree_node_id; + load_url_params.referrer = params.referrer; + load_url_params.redirect_chain = params.redirect_chain; + load_url_params.extra_headers = params.extra_headers; + load_url_params.is_renderer_initiated = params.is_renderer_initiated; + load_url_params.should_replace_current_entry = + params.should_replace_current_entry; + + if (params.uses_post) { + load_url_params.load_type = + content::NavigationController::LOAD_TYPE_HTTP_POST; + load_url_params.post_data = params.post_data; + } + + target->GetController().LoadURLWithParams(load_url_params); + return target; + } + private: HeadlessBrowserImpl* browser() { return headless_web_contents_->browser(); } @@ -226,6 +284,8 @@ HeadlessWebContentsImpl::HeadlessWebContentsImpl( #if BUILDFLAG(ENABLE_BASIC_PRINTING) && !defined(CHROME_MULTIPLE_DLL_CHILD) HeadlessPrintManager::CreateForWebContents(web_contents); #endif + web_contents->GetMutableRendererPrefs()->accept_languages = + browser_context->options()->accept_language(); web_contents_->SetDelegate(web_contents_delegate_.get()); render_process_host_->AddObserver(this); agent_host_->AddObserver(this); @@ -250,11 +310,8 @@ void HeadlessWebContentsImpl::CreateMojoService( void HeadlessWebContentsImpl::RenderFrameCreated( content::RenderFrameHost* render_frame_host) { - service_manager::BinderRegistry* interface_registry = - render_frame_host->GetInterfaceRegistry(); - for (const MojoService& service : mojo_services_) { - interface_registry->AddInterface( + registry_.AddInterface( service.service_name, base::Bind(&HeadlessWebContentsImpl::CreateMojoService, base::Unretained(this), service.service_factory), @@ -268,6 +325,13 @@ void HeadlessWebContentsImpl::RenderFrameCreated( headless_tab_socket_->RenderFrameCreated(render_frame_host); } +void HeadlessWebContentsImpl::OnInterfaceRequestFromFrame( + content::RenderFrameHost* render_frame_host, + const std::string& interface_name, + mojo::ScopedMessagePipeHandle* interface_pipe) { + registry_.TryBindInterface(interface_name, interface_pipe); +} + void HeadlessWebContentsImpl::RenderFrameDeleted( content::RenderFrameHost* render_frame_host) { if (headless_tab_socket_) @@ -293,10 +357,19 @@ HeadlessWebContentsImpl::GetUntrustedDevToolsFrameIdForFrameTreeNodeId( frame_tree_node_id); } +int HeadlessWebContentsImpl::GetFrameTreeNodeIdForDevToolsFrameId( + const std::string& devtools_id) const { + return browser_context_->GetFrameTreeNodeIdForDevToolsFrameId(devtools_id); +} + int HeadlessWebContentsImpl::GetMainFrameRenderProcessId() const { return web_contents()->GetMainFrame()->GetProcess()->GetID(); } +int HeadlessWebContentsImpl::GetMainFrameTreeNodeId() const { + return web_contents()->GetMainFrame()->GetFrameTreeNodeId(); +} + bool HeadlessWebContentsImpl::OpenURL(const GURL& url) { if (!url.is_valid()) return false; diff --git a/chromium/headless/lib/browser/headless_web_contents_impl.h b/chromium/headless/lib/browser/headless_web_contents_impl.h index 6ea8441bbcc..0b15f0f1578 100644 --- a/chromium/headless/lib/browser/headless_web_contents_impl.h +++ b/chromium/headless/lib/browser/headless_web_contents_impl.h @@ -18,6 +18,7 @@ #include "headless/public/headless_devtools_target.h" #include "headless/public/headless_export.h" #include "headless/public/headless_web_contents.h" +#include "services/service_manager/public/cpp/binder_registry.h" namespace content { class DevToolsAgentHost; @@ -63,7 +64,10 @@ class HEADLESS_EXPORT HeadlessWebContentsImpl std::string GetUntrustedDevToolsFrameIdForFrameTreeNodeId( int process_id, int frame_tree_node_id) const override; + int GetFrameTreeNodeIdForDevToolsFrameId( + const std::string& devtools_id) const override; int GetMainFrameRenderProcessId() const override; + int GetMainFrameTreeNodeId() const override; // HeadlessDevToolsTarget implementation: bool AttachClient(HeadlessDevToolsClient* client) override; @@ -87,6 +91,10 @@ class HEADLESS_EXPORT HeadlessWebContentsImpl void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override; void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override; void RenderViewReady() override; + void OnInterfaceRequestFromFrame( + content::RenderFrameHost* render_frame_host, + const std::string& interface_name, + mojo::ScopedMessagePipeHandle* interface_pipe) override; content::WebContents* web_contents() const; bool OpenURL(const GURL& url); @@ -147,6 +155,8 @@ class HEADLESS_EXPORT HeadlessWebContentsImpl base::ObserverList<HeadlessWebContents::Observer> observers_; + service_manager::BinderRegistry registry_; + base::WeakPtrFactory<HeadlessWebContentsImpl> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(HeadlessWebContentsImpl); diff --git a/chromium/headless/lib/dom_tree_extraction_expected_nodes.txt b/chromium/headless/lib/dom_tree_extraction_expected_nodes.txt index 1c3b2ee73db..5799ee09c77 100644 --- a/chromium/headless/lib/dom_tree_extraction_expected_nodes.txt +++ b/chromium/headless/lib/dom_tree_extraction_expected_nodes.txt @@ -8,7 +8,9 @@ "y": 0.0 }, "childNodeIndexes": [ 1 ], + "documentEncoding": "windows-1252", "documentURL": "http://127.0.0.1/dom_tree_test.html", + "frameId": "?", "layoutNodeIndex": 0, "nodeName": "#document", "nodeType": 9, @@ -222,7 +224,9 @@ "y": 0.0 }, "childNodeIndexes": [ 19 ], + "documentEncoding": "windows-1252", "documentURL": "http://127.0.0.1/iframe.html", + "frameId": "?", "layoutNodeIndex": 7, "nodeName": "#document", "nodeType": 9, diff --git a/chromium/headless/lib/frame_id_browsertest.cc b/chromium/headless/lib/frame_id_browsertest.cc index 5a03b396b2b..e68f70b2206 100644 --- a/chromium/headless/lib/frame_id_browsertest.cc +++ b/chromium/headless/lib/frame_id_browsertest.cc @@ -31,7 +31,7 @@ class TestProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { public: explicit TestProtocolHandler( scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner) - : test_delegate_(new TestDelegate(this)), + : test_delegate_(new TestDelegate()), dispatcher_(new ExpeditedDispatcher(io_thread_task_runner)), headless_browser_context_(nullptr) {} @@ -65,49 +65,42 @@ class TestProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { class MockURLFetcher : public URLFetcher { public: - explicit MockURLFetcher(const TestProtocolHandler* protocol_handler) + explicit MockURLFetcher(TestProtocolHandler* protocol_handler) : protocol_handler_(protocol_handler) {} ~MockURLFetcher() override {} // URLFetcher implementation: - void StartFetch(const GURL& url, - const std::string& method, - const std::string& post_data, - const net::HttpRequestHeaders& request_headers, + void StartFetch(const Request* request, ResultListener* result_listener) override { - EXPECT_EQ("GET", method); + GURL url = request->GetURLRequest()->url(); + EXPECT_EQ("GET", request->GetURLRequest()->method()); const Response* response = protocol_handler_->GetResponse(url.spec()); if (!response) result_listener->OnFetchStartError(net::ERR_FILE_NOT_FOUND); + net::LoadTimingInfo load_timing_info; result_listener->OnFetchCompleteExtractHeaders( - url, response->data.c_str(), response->data.size()); + url, response->data.c_str(), response->data.size(), load_timing_info); + + int frame_tree_node_id = request->GetFrameTreeNodeId(); + DCHECK_NE(frame_tree_node_id, -1) << " For url " << url; + protocol_handler_->url_to_frame_tree_node_id_[url.spec()] = + frame_tree_node_id; } private: - const TestProtocolHandler* protocol_handler_; + TestProtocolHandler* protocol_handler_; DISALLOW_COPY_AND_ASSIGN(MockURLFetcher); }; class TestDelegate : public GenericURLRequestJob::Delegate { public: - explicit TestDelegate(TestProtocolHandler* protocol_handler) - : protocol_handler_(protocol_handler) {} - + TestDelegate() {} ~TestDelegate() override {} // GenericURLRequestJob::Delegate implementation: - void OnPendingRequest(PendingRequest* pending_request) override { - const Request* request = pending_request->GetRequest(); - std::string url = request->GetURLRequest()->url().spec(); - int frame_tree_node_id = request->GetFrameTreeNodeId(); - DCHECK_NE(frame_tree_node_id, -1) << " For url " << url; - protocol_handler_->url_to_frame_tree_node_id_[url] = frame_tree_node_id; - pending_request->AllowRequest(); - } - void OnResourceLoadFailed(const Request* request, net::Error error) override {} @@ -119,7 +112,6 @@ class TestProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { size_t body_size) override {} private: - TestProtocolHandler* protocol_handler_; // NOT OWNED scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner_; DISALLOW_COPY_AND_ASSIGN(TestDelegate); @@ -131,8 +123,9 @@ class TestProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { net::NetworkDelegate* network_delegate) const override { return new GenericURLRequestJob( request, network_delegate, dispatcher_.get(), - base::MakeUnique<MockURLFetcher>(this), test_delegate_.get(), - headless_browser_context_); + base::MakeUnique<MockURLFetcher>( + const_cast<TestProtocolHandler*>(this)), + test_delegate_.get(), headless_browser_context_); } std::map<std::string, int> url_to_frame_tree_node_id_; @@ -256,6 +249,10 @@ class FrameIdTest : public HeadlessAsyncDevTooledBrowserTest, protocol_handler_url_to_frame_id_[pair.first] = web_contents_->GetUntrustedDevToolsFrameIdForFrameTreeNodeId( web_contents_->GetMainFrameRenderProcessId(), pair.second); + + EXPECT_EQ(pair.second, + web_contents_->GetFrameTreeNodeIdForDevToolsFrameId( + protocol_handler_url_to_frame_id_[pair.first])); } EXPECT_THAT(url_to_frame_id_, protocol_handler_url_to_frame_id_); diff --git a/chromium/headless/lib/headless_browser_browsertest.cc b/chromium/headless/lib/headless_browser_browsertest.cc index 1206725fb4a..86a5dacd1a6 100644 --- a/chromium/headless/lib/headless_browser_browsertest.cc +++ b/chromium/headless/lib/headless_browser_browsertest.cc @@ -11,8 +11,11 @@ #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_restrictions.h" +#include "build/build_config.h" #include "content/public/browser/permission_manager.h" #include "content/public/browser/permission_type.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/content_switches.h" #include "content/public/common/url_constants.h" #include "content/public/test/browser_test.h" #include "headless/lib/browser/headless_browser_context_impl.h" @@ -187,7 +190,6 @@ class HeadlessBrowserTestWithProxy : public HeadlessBrowserTest { public: HeadlessBrowserTestWithProxy() : proxy_server_(net::SpawnedTestServer::TYPE_HTTP, - net::SpawnedTestServer::kLocalhost, base::FilePath(FILE_PATH_LITERAL("headless/test/data"))) { } @@ -456,7 +458,7 @@ void URLRequestJobWithCookies::Start() { // See net::URLRequestHttpJob::AddCookieHeaderAndStart(). url::Origin requested_origin(request_->url()); - url::Origin site_for_cookies(request_->first_party_for_cookies()); + url::Origin site_for_cookies(request_->site_for_cookies()); if (net::registry_controlled_domains::SameDomainOrHost( requested_origin, site_for_cookies, @@ -609,7 +611,7 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, SetCookiesWithDevTools) { .SetSecure(true) .SetHttpOnly(true) .SetSameSite(network::CookieSameSite::EXACT) - .SetExpirationDate(0) + .SetExpires(0) .Build(); CookieSetter cookie_setter(this, web_contents, std::move(set_cookie_params)); @@ -644,9 +646,11 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, RendererCommandPrefixTest) { fprintf(launcher_file, "echo $@ > %s\n", launcher_stamp.value().c_str()); fprintf(launcher_file, "exec $@\n"); fclose(launcher_file); +#if !defined(OS_FUCHSIA) base::SetPosixFilePermissions(launcher_script, base::FILE_PERMISSION_READ_BY_USER | base::FILE_PERMISSION_EXECUTE_BY_USER); +#endif // !defined(OS_FUCHSIA) base::CommandLine::ForCurrentProcess()->AppendSwitch("--no-sandbox"); base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( @@ -913,4 +917,26 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, WindowPrint) { EvaluateScript(web_contents, "window.print()")->HasExceptionDetails()); } +IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, AllowInsecureLocalhostFlag) { + net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); + https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED); + https_server.ServeFilesFromSourceDirectory("headless/test/data"); + ASSERT_TRUE(https_server.Start()); + GURL test_url = https_server.GetURL("/hello.html"); + + base::CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kAllowInsecureLocalhost); + + HeadlessBrowserContext* browser_context = + browser()->CreateBrowserContextBuilder().Build(); + + HeadlessWebContentsImpl* web_contents = + HeadlessWebContentsImpl::From(browser_context->CreateWebContentsBuilder() + .SetInitialURL(test_url) + .Build()); + + // If the certificate fails to validate, this should fail. + EXPECT_TRUE(WaitForLoad(web_contents)); +} + } // namespace headless diff --git a/chromium/headless/lib/headless_content_main_delegate.cc b/chromium/headless/lib/headless_content_main_delegate.cc index 645b8ad3930..195ee8027d1 100644 --- a/chromium/headless/lib/headless_content_main_delegate.cc +++ b/chromium/headless/lib/headless_content_main_delegate.cc @@ -15,6 +15,7 @@ #include "base/run_loop.h" #include "base/strings/string_number_conversions.h" #include "base/trace_event/trace_event.h" +#include "build/build_config.h" #include "components/crash/content/app/breakpad_linux.h" #include "content/public/browser/browser_main_runner.h" #include "content/public/common/content_switches.h" @@ -22,6 +23,7 @@ #include "headless/lib/browser/headless_content_browser_client.h" #include "headless/lib/headless_crash_reporter_client.h" #include "headless/lib/headless_macros.h" +#include "headless/lib/utility/headless_content_utility_client.h" #include "ui/base/resource/resource_bundle.h" #include "ui/base/ui_base_switches.h" #include "ui/gfx/switches.h" @@ -50,8 +52,10 @@ const int kTraceEventBrowserProcessSortIndex = -6; HeadlessContentMainDelegate* g_current_headless_content_main_delegate = nullptr; +#if !defined(OS_FUCHSIA) base::LazyInstance<HeadlessCrashReporterClient>::Leaky g_headless_crash_client = LAZY_INSTANCE_INITIALIZER; +#endif const char kLogFileName[] = "CHROME_LOG_FILE"; } // namespace @@ -165,8 +169,14 @@ void HeadlessContentMainDelegate::InitLogging( DCHECK(success); } + void HeadlessContentMainDelegate::InitCrashReporter( const base::CommandLine& command_line) { +#if defined(OS_FUCHSIA) + // TODO(fuchsia): Implement this when crash reporting/Breakpad are available + // in Fuchsia. (crbug.com/753619) + NOTIMPLEMENTED(); +#else const std::string process_type = command_line.GetSwitchValueASCII(switches::kProcessType); crash_reporter::SetCrashReporterClient(g_headless_crash_client.Pointer()); @@ -187,10 +197,12 @@ void HeadlessContentMainDelegate::InitCrashReporter( // TODO(dvallet): Ideally we would also want to avoid this for component builds. #elif defined(OS_WIN) && !defined(CHROME_MULTIPLE_DLL) crash_reporter::InitializeCrashpadWithEmbeddedHandler(process_type.empty(), - process_type); + process_type, ""); #endif // defined(HEADLESS_USE_BREAKPAD) +#endif // defined(OS_FUCHSIA) } + void HeadlessContentMainDelegate::PreSandboxStartup() { const base::CommandLine& command_line( *base::CommandLine::ForCurrentProcess()); @@ -202,6 +214,7 @@ void HeadlessContentMainDelegate::PreSandboxStartup() { if (command_line.HasSwitch(switches::kEnableLogging)) InitLogging(command_line); #endif // defined(OS_WIN) + InitCrashReporter(command_line); InitializeResourceBundle(); } @@ -257,11 +270,6 @@ HeadlessContentMainDelegate* HeadlessContentMainDelegate::GetInstance() { // static void HeadlessContentMainDelegate::InitializeResourceBundle() { - base::FilePath dir_module; - base::FilePath pak_file; - bool result = PathService::Get(base::DIR_MODULE, &dir_module); - DCHECK(result); - base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); const std::string locale = command_line->GetSwitchValueASCII(switches::kLang); ui::ResourceBundle::InitSharedInstanceWithLocale( @@ -273,21 +281,51 @@ void HeadlessContentMainDelegate::InitializeResourceBundle() { reinterpret_cast<const char*>(kHeadlessResourcePak.contents), kHeadlessResourcePak.length), ui::SCALE_FACTOR_NONE); + #else + + base::FilePath dir_module; + bool result = PathService::Get(base::DIR_MODULE, &dir_module); + DCHECK(result); + // Try loading the headless library pak file first. If it doesn't exist (i.e., // when we're running with the --headless switch), fall back to the browser's // resource pak. - pak_file = dir_module.Append(FILE_PATH_LITERAL("headless_lib.pak")); - if (!base::PathExists(pak_file)) - pak_file = dir_module.Append(FILE_PATH_LITERAL("resources.pak")); + base::FilePath headless_pak = + dir_module.Append(FILE_PATH_LITERAL("headless_lib.pak")); + if (base::PathExists(headless_pak)) { + ResourceBundle::GetSharedInstance().AddDataPackFromPath( + headless_pak, ui::SCALE_FACTOR_NONE); + return; + } + + // Otherwise, load resources.pak, chrome_100 and chrome_200. + base::FilePath resources_pak = + dir_module.Append(FILE_PATH_LITERAL("resources.pak")); + base::FilePath chrome_100_pak = + dir_module.Append(FILE_PATH_LITERAL("chrome_100_percent.pak")); + base::FilePath chrome_200_pak = + dir_module.Append(FILE_PATH_LITERAL("chrome_200_percent.pak")); + #if defined(OS_MACOSX) && !defined(COMPONENT_BUILD) // In non component builds, check if fall back in Resources/ folder is // available. - if (!base::PathExists(pak_file)) - pak_file = dir_module.Append(FILE_PATH_LITERAL("Resources/resources.pak")); + if (!base::PathExists(resources_pak)) { + resources_pak = + dir_module.Append(FILE_PATH_LITERAL("Resources/resources.pak")); + chrome_100_pak = dir_module.Append( + FILE_PATH_LITERAL("Resources/chrome_100_percent.pak")); + chrome_200_pak = dir_module.Append( + FILE_PATH_LITERAL("Resources/chrome_200_percent.pak")); + } #endif + + ResourceBundle::GetSharedInstance().AddDataPackFromPath( + resources_pak, ui::SCALE_FACTOR_NONE); ResourceBundle::GetSharedInstance().AddDataPackFromPath( - pak_file, ui::SCALE_FACTOR_NONE); + chrome_100_pak, ui::SCALE_FACTOR_100P); + ResourceBundle::GetSharedInstance().AddDataPackFromPath( + chrome_200_pak, ui::SCALE_FACTOR_200P); #endif } @@ -306,6 +344,13 @@ HeadlessContentMainDelegate::CreateContentRendererClient() { renderer_client_ = base::MakeUnique<HeadlessContentRendererClient>(); return renderer_client_.get(); } + +content::ContentUtilityClient* +HeadlessContentMainDelegate::CreateContentUtilityClient() { + utility_client_ = base::MakeUnique<HeadlessContentUtilityClient>( + browser_->options()->user_agent); + return utility_client_.get(); +} #endif // !defined(CHROME_MULTIPLE_DLL_BROWSER) } // namespace headless diff --git a/chromium/headless/lib/headless_content_main_delegate.h b/chromium/headless/lib/headless_content_main_delegate.h index eb8cd61eea6..37d935177fc 100644 --- a/chromium/headless/lib/headless_content_main_delegate.h +++ b/chromium/headless/lib/headless_content_main_delegate.h @@ -10,6 +10,7 @@ #include "base/compiler_specific.h" #include "base/macros.h" +#include "build/build_config.h" #include "content/public/app/content_main_delegate.h" #include "content/public/browser/content_browser_client.h" #include "content/public/renderer/content_renderer_client.h" @@ -40,6 +41,7 @@ class HEADLESS_EXPORT HeadlessContentMainDelegate const std::string& process_type, const content::MainFunctionParams& main_function_params) override; content::ContentBrowserClient* CreateContentBrowserClient() override; + content::ContentUtilityClient* CreateContentUtilityClient() override; content::ContentRendererClient* CreateContentRendererClient() override; HeadlessBrowserImpl* browser() const { return browser_.get(); } @@ -51,14 +53,15 @@ class HEADLESS_EXPORT HeadlessContentMainDelegate private: friend class HeadlessBrowserTest; - void InitLogging(const base::CommandLine& command_line); - void InitCrashReporter(const base::CommandLine& command_line); static void InitializeResourceBundle(); - static HeadlessContentMainDelegate* GetInstance(); + void InitLogging(const base::CommandLine& command_line); + void InitCrashReporter(const base::CommandLine& command_line); + std::unique_ptr<content::ContentRendererClient> renderer_client_; std::unique_ptr<content::ContentBrowserClient> browser_client_; + std::unique_ptr<content::ContentUtilityClient> utility_client_; HeadlessContentClient content_client_; HeadlessPlatformEventSource platform_event_source_; diff --git a/chromium/headless/lib/headless_content_main_delegate_win.cc b/chromium/headless/lib/headless_content_main_delegate_win.cc index 084683204ba..90460197995 100644 --- a/chromium/headless/lib/headless_content_main_delegate_win.cc +++ b/chromium/headless/lib/headless_content_main_delegate_win.cc @@ -30,6 +30,11 @@ content::ContentRendererClient* HeadlessContentMainDelegate::CreateContentRendererClient() { return nullptr; } + +content::ContentUtilityClient* +HeadlessContentMainDelegate::CreateContentUtilityClient() { + return nullptr; +} #endif // defined(CHROME_MULTIPLE_DLL_BROWSER) } // namespace headless diff --git a/chromium/headless/lib/headless_devtools_client_browsertest.cc b/chromium/headless/lib/headless_devtools_client_browsertest.cc index 8a4e5491cc2..1e97640027c 100644 --- a/chromium/headless/lib/headless_devtools_client_browsertest.cc +++ b/chromium/headless/lib/headless_devtools_client_browsertest.cc @@ -459,11 +459,7 @@ class TargetDomainCreateAndDeletePageTest } }; -#if defined(OS_WIN) -DISABLED_HEADLESS_ASYNC_DEVTOOLED_TEST_F(TargetDomainCreateAndDeletePageTest); -#else HEADLESS_ASYNC_DEVTOOLED_TEST_F(TargetDomainCreateAndDeletePageTest); -#endif class TargetDomainCreateAndDeleteBrowserContextTest : public HeadlessAsyncDevTooledBrowserTest { @@ -531,12 +527,7 @@ class TargetDomainCreateAndDeleteBrowserContextTest std::string browser_context_id_; }; -#if defined(OS_WIN) -DISABLED_HEADLESS_ASYNC_DEVTOOLED_TEST_F( - TargetDomainCreateAndDeleteBrowserContextTest); -#else HEADLESS_ASYNC_DEVTOOLED_TEST_F(TargetDomainCreateAndDeleteBrowserContextTest); -#endif class TargetDomainDisposeContextFailsIfInUse : public HeadlessAsyncDevTooledBrowserTest { @@ -612,12 +603,7 @@ class TargetDomainDisposeContextFailsIfInUse std::string page_id_; }; -#if defined(OS_WIN) -DISABLED_HEADLESS_ASYNC_DEVTOOLED_TEST_F( - TargetDomainDisposeContextFailsIfInUse); -#else HEADLESS_ASYNC_DEVTOOLED_TEST_F(TargetDomainDisposeContextFailsIfInUse); -#endif class TargetDomainCreateTwoContexts : public HeadlessAsyncDevTooledBrowserTest, public target::ExperimentalObserver, @@ -889,41 +875,44 @@ class TargetDomainCreateTwoContexts : public HeadlessAsyncDevTooledBrowserTest, int context_closed_count_ = 0; }; -#if defined(OS_WIN) -DISABLED_HEADLESS_ASYNC_DEVTOOLED_TEST_F(TargetDomainCreateTwoContexts); -#else HEADLESS_ASYNC_DEVTOOLED_TEST_F(TargetDomainCreateTwoContexts); -#endif class HeadlessDevToolsNavigationControlTest : public HeadlessAsyncDevTooledBrowserTest, + network::ExperimentalObserver, page::ExperimentalObserver { public: void RunDevTooledTest() override { EXPECT_TRUE(embedded_test_server()->Start()); base::RunLoop run_loop; devtools_client_->GetPage()->GetExperimental()->AddObserver(this); + devtools_client_->GetNetwork()->GetExperimental()->AddObserver(this); devtools_client_->GetPage()->Enable(run_loop.QuitClosure()); base::MessageLoop::ScopedNestableTaskAllower nest_loop( base::MessageLoop::current()); run_loop.Run(); - devtools_client_->GetPage()->GetExperimental()->SetControlNavigations( - headless::page::SetControlNavigationsParams::Builder() - .SetEnabled(true) - .Build()); + devtools_client_->GetNetwork()->Enable(); + devtools_client_->GetNetwork() + ->GetExperimental() + ->SetRequestInterceptionEnabled( + headless::network::SetRequestInterceptionEnabledParams::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; + void OnRequestIntercepted( + const network::RequestInterceptedParams& params) override { + if (params.GetIsNavigationRequest()) + 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()); + devtools_client_->GetNetwork() + ->GetExperimental() + ->ContinueInterceptedRequest( + headless::network::ContinueInterceptedRequestParams::Builder() + .SetInterceptionId(params.GetInterceptionId()) + .Build()); } void OnFrameStoppedLoading( @@ -1386,7 +1375,7 @@ class DomTreeExtractionBrowserTest : public HeadlessAsyncDevTooledBrowserTest, HEADLESS_ASYNC_DEVTOOLED_TEST_F(DomTreeExtractionBrowserTest); -class FailedUrlRequestTest : public HeadlessAsyncDevTooledBrowserTest, +class UrlRequestFailedTest : public HeadlessAsyncDevTooledBrowserTest, public HeadlessBrowserContext::Observer, public network::ExperimentalObserver, public page::Observer { @@ -1413,49 +1402,77 @@ class FailedUrlRequestTest : public HeadlessAsyncDevTooledBrowserTest, run_loop.Run(); devtools_client_->GetPage()->Navigate( - embedded_test_server()->GetURL("/dom_tree_test.html").spec()); + embedded_test_server()->GetURL("/resource_cancel_test.html").spec()); + } + + bool ShouldCancel(const std::string& url) { + if (EndsWith(url, "/iframe.html", base::CompareCase::INSENSITIVE_ASCII)) + return true; + + if (EndsWith(url, "/test.jpg", base::CompareCase::INSENSITIVE_ASCII)) + return true; + + return false; } void OnRequestIntercepted( const network::RequestInterceptedParams& params) override { - if (EndsWith(params.GetRequest()->GetUrl(), "/iframe.html", - base::CompareCase::INSENSITIVE_ASCII)) { - // Block the iframe resource load. - devtools_client_->GetNetwork() - ->GetExperimental() - ->ContinueInterceptedRequest( - network::ContinueInterceptedRequestParams::Builder() - .SetInterceptionId(params.GetInterceptionId()) - .SetErrorReason(network::ErrorReason::ABORTED) - .Build()); - } else { - // Allow everything else to continue. - devtools_client_->GetNetwork() - ->GetExperimental() - ->ContinueInterceptedRequest( - network::ContinueInterceptedRequestParams::Builder() - .SetInterceptionId(params.GetInterceptionId()) - .Build()); - } + urls_seen_.push_back(GURL(params.GetRequest()->GetUrl()).ExtractFileName()); + auto continue_intercept_params = + network::ContinueInterceptedRequestParams::Builder() + .SetInterceptionId(params.GetInterceptionId()) + .Build(); + if (ShouldCancel(params.GetRequest()->GetUrl())) + continue_intercept_params->SetErrorReason(GetErrorReason()); + + devtools_client_->GetNetwork() + ->GetExperimental() + ->ContinueInterceptedRequest(std::move(continue_intercept_params)); } void OnLoadEventFired(const page::LoadEventFiredParams&) override { + browser_context_->RemoveObserver(this); + base::AutoLock lock(lock_); - EXPECT_EQ("iframe.html", url_that_failed_to_load_); + EXPECT_THAT(urls_that_failed_to_load_, + ElementsAre("test.jpg", "iframe.html")); + EXPECT_THAT( + urls_seen_, + ElementsAre("resource_cancel_test.html", "dom_tree_test.css", + "test.jpg", "iframe.html", "iframe2.html", "Ahem.ttf")); FinishAsynchronousTest(); } void UrlRequestFailed(net::URLRequest* request, int net_error) override { base::AutoLock lock(lock_); - url_that_failed_to_load_ = request->url().ExtractFileName(); + urls_that_failed_to_load_.push_back(request->url().ExtractFileName()); } + virtual network::ErrorReason GetErrorReason() = 0; + private: base::Lock lock_; - std::string url_that_failed_to_load_; + std::vector<std::string> urls_seen_; + std::vector<std::string> urls_that_failed_to_load_; +}; + +class UrlRequestFailedTest_Failed : public UrlRequestFailedTest { + public: + network::ErrorReason GetErrorReason() override { + return network::ErrorReason::FAILED; + } }; -HEADLESS_ASYNC_DEVTOOLED_TEST_F(FailedUrlRequestTest); +HEADLESS_ASYNC_DEVTOOLED_TEST_F(UrlRequestFailedTest_Failed); + +class UrlRequestFailedTest_Abort : public UrlRequestFailedTest { + public: + network::ErrorReason GetErrorReason() override { + return network::ErrorReason::ABORTED; + } +}; + +HEADLESS_ASYNC_DEVTOOLED_TEST_F(UrlRequestFailedTest_Abort); class DevToolsSetCookieTest : public HeadlessAsyncDevTooledBrowserTest, public network::Observer { @@ -1491,7 +1508,6 @@ class DevtoolsInterceptionWithAuthProxyTest public: DevtoolsInterceptionWithAuthProxyTest() : proxy_server_(net::SpawnedTestServer::TYPE_BASIC_AUTH_PROXY, - net::SpawnedTestServer::kLocalhost, base::FilePath(FILE_PATH_LITERAL("headless/test/data"))) { } @@ -1560,11 +1576,12 @@ class DevtoolsInterceptionWithAuthProxyTest FinishAsynchronousTest(); } - std::unique_ptr<net::ProxyConfig> GetProxyConfig() override { + void CustomizeHeadlessBrowserContext( + HeadlessBrowserContext::Builder& builder) override { std::unique_ptr<net::ProxyConfig> proxy_config(new net::ProxyConfig); proxy_config->proxy_rules().ParseFromString( proxy_server_.host_port_pair().ToString()); - return proxy_config; + builder.SetProxyConfig(std::move(proxy_config)); } private: @@ -1575,4 +1592,28 @@ class DevtoolsInterceptionWithAuthProxyTest HEADLESS_ASYNC_DEVTOOLED_TEST_F(DevtoolsInterceptionWithAuthProxyTest); +class NavigatorLanguages : public HeadlessAsyncDevTooledBrowserTest { + public: + void RunDevTooledTest() override { + devtools_client_->GetRuntime()->Evaluate( + "JSON.stringify(navigator.languages)", + base::Bind(&NavigatorLanguages::OnResult, base::Unretained(this))); + } + + void OnResult(std::unique_ptr<runtime::EvaluateResult> result) { + std::string value; + EXPECT_TRUE(result->GetResult()->HasValue()); + EXPECT_TRUE(result->GetResult()->GetValue()->GetAsString(&value)); + EXPECT_EQ("[\"en-UK\",\"DE\",\"FR\"]", value); + FinishAsynchronousTest(); + } + + void CustomizeHeadlessBrowserContext( + HeadlessBrowserContext::Builder& builder) override { + builder.SetAcceptLanguage("en-UK, DE, FR"); + } +}; + +HEADLESS_ASYNC_DEVTOOLED_TEST_F(NavigatorLanguages); + } // namespace headless diff --git a/chromium/headless/lib/headless_macros.h b/chromium/headless/lib/headless_macros.h index 4b7bedb7a9c..f0dea81eb17 100644 --- a/chromium/headless/lib/headless_macros.h +++ b/chromium/headless/lib/headless_macros.h @@ -5,8 +5,10 @@ #ifndef HEADLESS_LIB_HEADLESS_MACROS_H_ #define HEADLESS_LIB_HEADLESS_MACROS_H_ -#if defined(OS_POSIX) && !defined(OS_MACOSX) +#include "build/build_config.h" + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_FUCHSIA) #define HEADLESS_USE_BREAKPAD -#endif // defined(OS_POSIX) && !defined(OS_MACOSX) +#endif // defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_FUCHSIA) #endif // HEADLESS_LIB_HEADLESS_MACROS_H_ diff --git a/chromium/headless/lib/headless_web_contents_browsertest.cc b/chromium/headless/lib/headless_web_contents_browsertest.cc index 0f2ed053beb..d852948ecca 100644 --- a/chromium/headless/lib/headless_web_contents_browsertest.cc +++ b/chromium/headless/lib/headless_web_contents_browsertest.cc @@ -8,6 +8,8 @@ #include "base/base64.h" #include "base/json/json_writer.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "content/public/browser/web_contents.h" @@ -117,6 +119,8 @@ IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, WindowOpen) { EXPECT_EQ(expected_bounds.size(), child->web_contents()->GetContainerBounds().size()); #endif // !defined(OS_MACOSX) + + browser_context->RemoveObserver(&observer); } class HeadlessWindowOpenTabSocketTest : public HeadlessBrowserTest, @@ -239,6 +243,8 @@ IN_PROC_BROWSER_TEST_F(HeadlessWindowOpenTabSocketTest, RunAsynchronousTest(); EXPECT_EQ("Embedder sent us: One", message_); + + browser_context->RemoveObserver(this); } class HeadlessNoDevToolsTabSocketTest : public HeadlessBrowserTest, @@ -860,4 +866,51 @@ class HeadlessWebContentsRequestStorageQuotaTest HEADLESS_ASYNC_DEVTOOLED_TEST_F(HeadlessWebContentsRequestStorageQuotaTest); +IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, BrowserTabChangeContent) { + EXPECT_TRUE(embedded_test_server()->Start()); + + HeadlessBrowserContext* browser_context = + browser()->CreateBrowserContextBuilder().Build(); + + HeadlessWebContents* web_contents = + browser_context->CreateWebContentsBuilder().Build(); + EXPECT_TRUE(WaitForLoad(web_contents)); + + std::string script = "window.location = '" + + embedded_test_server()->GetURL("/hello.html").spec() + + "';"; + EXPECT_FALSE(EvaluateScript(web_contents, script)->HasExceptionDetails()); + + // This will time out if the previous script did not work. + EXPECT_TRUE(WaitForLoad(web_contents)); +} + +IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, BrowserOpenInTab) { + EXPECT_TRUE(embedded_test_server()->Start()); + + HeadlessBrowserContext* browser_context = + browser()->CreateBrowserContextBuilder().Build(); + + MockHeadlessBrowserContextObserver observer; + browser_context->AddObserver(&observer); + EXPECT_CHILD_CONTENTS_CREATED(observer); + + HeadlessWebContents* web_contents = + browser_context->CreateWebContentsBuilder() + .SetInitialURL(embedded_test_server()->GetURL("/link.html")) + .Build(); + EXPECT_TRUE(WaitForLoad(web_contents)); + + EXPECT_EQ(1u, browser_context->GetAllWebContents().size()); + // Simulates a middle-button click on a link to ensure that the + // link is opened in a new tab by the browser and not by the renderer. + std::string script = + "var event = new MouseEvent('click', {'button': 1});" + "document.getElementsByTagName('a')[0].dispatchEvent(event);"; + EXPECT_FALSE(EvaluateScript(web_contents, script)->HasExceptionDetails()); + + // Check that we have a new tab. + EXPECT_EQ(2u, browser_context->GetAllWebContents().size()); + browser_context->RemoveObserver(&observer); +} } // namespace headless diff --git a/chromium/headless/lib/renderer/OWNERS b/chromium/headless/lib/renderer/OWNERS new file mode 100644 index 00000000000..b81b2a7da6e --- /dev/null +++ b/chromium/headless/lib/renderer/OWNERS @@ -0,0 +1,2 @@ +per-file headless_renderer_manifest_overlay.json=set noparent +per-file headless_renderer_manifest_overlay.json=file://ipc/SECURITY_OWNERS diff --git a/chromium/headless/lib/renderer/headless_content_renderer_client.cc b/chromium/headless/lib/renderer/headless_content_renderer_client.cc index a438df42a2e..ce73e4d1ac9 100644 --- a/chromium/headless/lib/renderer/headless_content_renderer_client.cc +++ b/chromium/headless/lib/renderer/headless_content_renderer_client.cc @@ -9,8 +9,8 @@ #include "printing/features/features.h" #if BUILDFLAG(ENABLE_BASIC_PRINTING) -#include "components/printing/renderer/print_web_view_helper.h" -#include "headless/lib/renderer/headless_print_web_view_helper_delegate.h" +#include "components/printing/renderer/print_render_frame_helper.h" +#include "headless/lib/renderer/headless_print_render_frame_helper_delegate.h" #endif namespace headless { @@ -22,8 +22,8 @@ HeadlessContentRendererClient::~HeadlessContentRendererClient() {} void HeadlessContentRendererClient::RenderFrameCreated( content::RenderFrame* render_frame) { #if BUILDFLAG(ENABLE_BASIC_PRINTING) - new printing::PrintWebViewHelper( - render_frame, base::MakeUnique<HeadlessPrintWebViewHelperDelegate>()); + new printing::PrintRenderFrameHelper( + render_frame, base::MakeUnique<HeadlessPrintRenderFrameHelperDelegate>()); #endif new HeadlessRenderFrameControllerImpl(render_frame); } diff --git a/chromium/headless/lib/renderer/headless_print_render_frame_helper_delegate.cc b/chromium/headless/lib/renderer/headless_print_render_frame_helper_delegate.cc new file mode 100644 index 00000000000..b3dc8acf2dc --- /dev/null +++ b/chromium/headless/lib/renderer/headless_print_render_frame_helper_delegate.cc @@ -0,0 +1,46 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "headless/lib/renderer/headless_print_render_frame_helper_delegate.h" + +#include "third_party/WebKit/public/web/WebElement.h" + +namespace headless { + +HeadlessPrintRenderFrameHelperDelegate:: + HeadlessPrintRenderFrameHelperDelegate() {} + +HeadlessPrintRenderFrameHelperDelegate:: + ~HeadlessPrintRenderFrameHelperDelegate() {} + +bool HeadlessPrintRenderFrameHelperDelegate::CancelPrerender( + content::RenderFrame* render_frame) { + return false; +} + +blink::WebElement HeadlessPrintRenderFrameHelperDelegate::GetPdfElement( + blink::WebLocalFrame* frame) { + return blink::WebElement(); +} + +bool HeadlessPrintRenderFrameHelperDelegate::IsPrintPreviewEnabled() { + return false; +} + +bool HeadlessPrintRenderFrameHelperDelegate::OverridePrint( + blink::WebLocalFrame* frame) { + return false; +} + +bool HeadlessPrintRenderFrameHelperDelegate::IsAskPrintSettingsEnabled() { + return true; +} + +#if defined(OS_MACOSX) +bool HeadlessPrintRenderFrameHelperDelegate::UseSingleMetafile() { + return true; +} +#endif + +} // namespace headless diff --git a/chromium/headless/lib/renderer/headless_print_render_frame_helper_delegate.h b/chromium/headless/lib/renderer/headless_print_render_frame_helper_delegate.h new file mode 100644 index 00000000000..7e7b3136325 --- /dev/null +++ b/chromium/headless/lib/renderer/headless_print_render_frame_helper_delegate.h @@ -0,0 +1,33 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef HEADLESS_LIB_RENDERER_HEADLESS_PRINT_RENDER_FRAME_HELPER_DELEGATE_H_ +#define HEADLESS_LIB_RENDERER_HEADLESS_PRINT_RENDER_FRAME_HELPER_DELEGATE_H_ + +#include "build/build_config.h" +#include "components/printing/renderer/print_render_frame_helper.h" + +namespace headless { + +class HeadlessPrintRenderFrameHelperDelegate + : public printing::PrintRenderFrameHelper::Delegate { + public: + HeadlessPrintRenderFrameHelperDelegate(); + ~HeadlessPrintRenderFrameHelperDelegate() override; + + // PrintRenderFrameHelper Delegate implementation. + bool CancelPrerender(content::RenderFrame* render_frame) override; + bool IsPrintPreviewEnabled() override; + bool OverridePrint(blink::WebLocalFrame* frame) override; + bool IsAskPrintSettingsEnabled() override; + blink::WebElement GetPdfElement(blink::WebLocalFrame* frame) override; + +#if defined(OS_MACOSX) + bool UseSingleMetafile() override; +#endif +}; + +} // namespace headless + +#endif // HEADLESS_LIB_RENDERER_HEADLESS_PRINT_RENDER_FRAME_HELPER_DELEGATE_H_ diff --git a/chromium/headless/lib/renderer/headless_print_web_view_helper_delegate.cc b/chromium/headless/lib/renderer/headless_print_web_view_helper_delegate.cc deleted file mode 100644 index 4d78ea29b70..00000000000 --- a/chromium/headless/lib/renderer/headless_print_web_view_helper_delegate.cc +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "headless/lib/renderer/headless_print_web_view_helper_delegate.h" - -#include "third_party/WebKit/public/web/WebElement.h" - -namespace headless { - -HeadlessPrintWebViewHelperDelegate::HeadlessPrintWebViewHelperDelegate() {} - -HeadlessPrintWebViewHelperDelegate::~HeadlessPrintWebViewHelperDelegate() {} - -bool HeadlessPrintWebViewHelperDelegate::CancelPrerender( - content::RenderFrame* render_frame) { - return false; -} - -blink::WebElement HeadlessPrintWebViewHelperDelegate::GetPdfElement( - blink::WebLocalFrame* frame) { - return blink::WebElement(); -} - -bool HeadlessPrintWebViewHelperDelegate::IsPrintPreviewEnabled() { - return false; -} - -bool HeadlessPrintWebViewHelperDelegate::OverridePrint( - blink::WebLocalFrame* frame) { - return false; -} - -bool HeadlessPrintWebViewHelperDelegate::IsAskPrintSettingsEnabled() { - return true; -} - -#if defined(OS_MACOSX) -bool HeadlessPrintWebViewHelperDelegate::UseSingleMetafile() { - return true; -} -#endif - -} // namespace headless diff --git a/chromium/headless/lib/renderer/headless_print_web_view_helper_delegate.h b/chromium/headless/lib/renderer/headless_print_web_view_helper_delegate.h deleted file mode 100644 index 691974c029b..00000000000 --- a/chromium/headless/lib/renderer/headless_print_web_view_helper_delegate.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef HEADLESS_LIB_RENDERER_HEADLESS_PRINT_WEB_VIEW_HELPER_DELEGATE_H_ -#define HEADLESS_LIB_RENDERER_HEADLESS_PRINT_WEB_VIEW_HELPER_DELEGATE_H_ - -#include "components/printing/renderer/print_web_view_helper.h" - -namespace headless { - -class HeadlessPrintWebViewHelperDelegate - : public printing::PrintWebViewHelper::Delegate { - public: - HeadlessPrintWebViewHelperDelegate(); - ~HeadlessPrintWebViewHelperDelegate() override; - - // PrintWebViewHelper Delegate implementation. - bool CancelPrerender(content::RenderFrame* render_frame) override; - bool IsPrintPreviewEnabled() override; - bool OverridePrint(blink::WebLocalFrame* frame) override; - bool IsAskPrintSettingsEnabled() override; - blink::WebElement GetPdfElement(blink::WebLocalFrame* frame) override; - -#if defined(OS_MACOSX) - bool UseSingleMetafile() override; -#endif -}; - -} // namespace headless - -#endif // HEADLESS_LIB_RENDERER_HEADLESS_PRINT_WEB_VIEW_HELPER_DELEGATE_H_ diff --git a/chromium/headless/lib/renderer/headless_render_frame_controller_impl.cc b/chromium/headless/lib/renderer/headless_render_frame_controller_impl.cc index 3ec1b4c5748..18b980b4749 100644 --- a/chromium/headless/lib/renderer/headless_render_frame_controller_impl.cc +++ b/chromium/headless/lib/renderer/headless_render_frame_controller_impl.cc @@ -16,7 +16,7 @@ HeadlessRenderFrameControllerImpl::HeadlessRenderFrameControllerImpl( : content::RenderFrameObserver(render_frame), render_frame_(render_frame), weak_ptr_factory_(this) { - render_frame->GetInterfaceRegistry()->AddInterface(base::Bind( + registry_.AddInterface(base::Bind( &HeadlessRenderFrameControllerImpl::OnRenderFrameControllerRequest, base::Unretained(this))); } @@ -68,6 +68,12 @@ void HeadlessRenderFrameControllerImpl::SendMessageToTabSocket( find_it->second.OnMessageFromEmbedder(message); } +void HeadlessRenderFrameControllerImpl::OnInterfaceRequestForFrame( + const std::string& interface_name, + mojo::ScopedMessagePipeHandle* interface_pipe) { + registry_.TryBindInterface(interface_name, interface_pipe); +} + void HeadlessRenderFrameControllerImpl::DidCreateScriptContext( v8::Local<v8::Context> context, int world_id) { diff --git a/chromium/headless/lib/renderer/headless_render_frame_controller_impl.h b/chromium/headless/lib/renderer/headless_render_frame_controller_impl.h index 5e106e8ef73..dba52acc74a 100644 --- a/chromium/headless/lib/renderer/headless_render_frame_controller_impl.h +++ b/chromium/headless/lib/renderer/headless_render_frame_controller_impl.h @@ -11,6 +11,7 @@ #include "headless/lib/renderer/headless_tab_socket_bindings.h" #include "headless/lib/tab_socket.mojom.h" #include "mojo/public/cpp/bindings/binding_set.h" +#include "services/service_manager/public/cpp/binder_registry.h" namespace headless { @@ -33,6 +34,9 @@ class HeadlessRenderFrameControllerImpl : public HeadlessRenderFrameController, int32_t world_id) override; // content::RenderFrameObserver implementation: + void OnInterfaceRequestForFrame( + const std::string& interface_name, + mojo::ScopedMessagePipeHandle* interface_pipe) override; void DidCreateScriptContext(v8::Local<v8::Context> context, int world_id) override; @@ -51,6 +55,7 @@ class HeadlessRenderFrameControllerImpl : public HeadlessRenderFrameController, headless::TabSocketPtr tab_socket_ptr_; InstallMainWorldTabSocketCallback pending_install_main_world_tab_socket_callback_; + service_manager::BinderRegistry registry_; base::WeakPtrFactory<HeadlessRenderFrameControllerImpl> weak_ptr_factory_; }; diff --git a/chromium/headless/lib/resources/headless_lib_resources.grd b/chromium/headless/lib/resources/headless_lib_resources.grd index 78f34cd1e84..9322c4ca31f 100644 --- a/chromium/headless/lib/resources/headless_lib_resources.grd +++ b/chromium/headless/lib/resources/headless_lib_resources.grd @@ -10,9 +10,11 @@ <release seq="1"> <includes> <include name="IDR_HEADLESS_LIB_DEVTOOLS_DISCOVERY_PAGE" file="devtools_discovery_page.html" type="BINDATA" /> - <include name="IDR_HEADLESS_BROWSER_MANIFEST_OVERLAY_TEMPLATE" file="../browser/headless_browser_manifest_overlay_template.json" type="BINDATA" /> + <include name="IDR_HEADLESS_BROWSER_MANIFEST_OVERLAY" file="${mojom_root}/headless/headless_browser_manifest_overlay.json" use_base_dir="false" type="BINDATA" /> <include name="IDR_HEADLESS_RENDERER_MANIFEST_OVERLAY" file="../renderer/headless_renderer_manifest_overlay.json" type="BINDATA" /> - <include name="IDR_HEADLESS_TAB_SOCKET_MOJOM_JS" file="${mojom_root}\headless\lib\tab_socket.mojom.js" use_base_dir="false" type="BINDATA" /> + <include name="IDR_HEADLESS_PACKAGED_SERVICES_MANIFEST_OVERLAY" file="${mojom_root}/headless/headless_packaged_services_manifest_overlay.json" use_base_dir="false" type="BINDATA" /> + <include name="IDR_HEADLESS_TAB_SOCKET_MOJOM_JS" file="${mojom_root}/headless/lib/tab_socket.mojom.js" use_base_dir="false" type="BINDATA" /> + <include name="IDR_PDF_COMPOSITOR_MANIFEST" file="${mojom_root}/components/printing/service/pdf_compositor_manifest.json" use_base_dir="false" type="BINDATA" /> </includes> </release> </grit> diff --git a/chromium/headless/lib/utility/DEPS b/chromium/headless/lib/utility/DEPS new file mode 100644 index 00000000000..2d935b53099 --- /dev/null +++ b/chromium/headless/lib/utility/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+components/printing/service/", + "+content/public/utility", +] diff --git a/chromium/headless/lib/utility/headless_content_utility_client.cc b/chromium/headless/lib/utility/headless_content_utility_client.cc new file mode 100644 index 00000000000..a2ddaee3d87 --- /dev/null +++ b/chromium/headless/lib/utility/headless_content_utility_client.cc @@ -0,0 +1,32 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "headless/lib/utility/headless_content_utility_client.h" + +#include "printing/features/features.h" + +#if BUILDFLAG(ENABLE_BASIC_PRINTING) +#include "components/printing/service/public/cpp/pdf_compositor_service_factory.h" +#include "components/printing/service/public/interfaces/pdf_compositor.mojom.h" +#endif + +namespace headless { + +HeadlessContentUtilityClient::HeadlessContentUtilityClient( + const std::string& user_agent) + : user_agent_(user_agent) {} + +HeadlessContentUtilityClient::~HeadlessContentUtilityClient() {} + +void HeadlessContentUtilityClient::RegisterServices( + HeadlessContentUtilityClient::StaticServiceMap* services) { +#if BUILDFLAG(ENABLE_BASIC_PRINTING) && !defined(CHROME_MULTIPLE_DLL_BROWSER) + service_manager::EmbeddedServiceInfo pdf_compositor_info; + pdf_compositor_info.factory = + base::Bind(&printing::CreatePdfCompositorService, user_agent_); + services->emplace(printing::mojom::kServiceName, pdf_compositor_info); +#endif +} + +} // namespace headless diff --git a/chromium/headless/lib/utility/headless_content_utility_client.h b/chromium/headless/lib/utility/headless_content_utility_client.h new file mode 100644 index 00000000000..f577dc2c7c6 --- /dev/null +++ b/chromium/headless/lib/utility/headless_content_utility_client.h @@ -0,0 +1,30 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef HEADLESS_LIB_HEADLESS_CONTENT_UTILITY_CLIENT_H_ +#define HEADLESS_LIB_HEADLESS_CONTENT_UTILITY_CLIENT_H_ + +#include <string> + +#include "content/public/utility/content_utility_client.h" + +namespace headless { + +class HeadlessContentUtilityClient : public content::ContentUtilityClient { + public: + explicit HeadlessContentUtilityClient(const std::string& user_agent); + ~HeadlessContentUtilityClient() override; + + // content::ContentUtilityClient: + void RegisterServices(StaticServiceMap* services) override; + + private: + const std::string user_agent_; + + DISALLOW_COPY_AND_ASSIGN(HeadlessContentUtilityClient); +}; + +} // namespace headless + +#endif // HEADLESS_LIB_HEADLESS_CONTENT_UTILITY_CLIENT_H_ diff --git a/chromium/headless/lib/virtual_time_browsertest.cc b/chromium/headless/lib/virtual_time_browsertest.cc index 3da3d0f2a0d..b856a72ce78 100644 --- a/chromium/headless/lib/virtual_time_browsertest.cc +++ b/chromium/headless/lib/virtual_time_browsertest.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include <memory> +#include "base/strings/stringprintf.h" #include "content/public/test/browser_test.h" #include "headless/public/devtools/domains/emulation.h" #include "headless/public/devtools/domains/page.h" @@ -51,20 +52,26 @@ class VirtualTimeBrowserTest : public HeadlessAsyncDevTooledBrowserTest, std::string message; if (args.size() == 1u && args[0]->HasValue() && args[0]->GetValue()->GetAsString(&message)) { - console_logs_.push_back(message); + log_.push_back(message); } } // emulation::Observer implementation: void OnVirtualTimeBudgetExpired( const emulation::VirtualTimeBudgetExpiredParams& params) override { - EXPECT_THAT(console_logs_, - ElementsAre("step1", "step2", "step3", "step4", "pass")); - + EXPECT_THAT(log_, + ElementsAre("Paused @ 0ms", "step1", "step2", "Paused @ 100ms", + "step3", "Paused @ 200ms", "step4", "pass")); FinishAsynchronousTest(); } - std::vector<std::string> console_logs_; + void OnVirtualTimePaused( + const emulation::VirtualTimePausedParams& params) override { + log_.push_back( + base::StringPrintf("Paused @ %dms", params.GetVirtualTimeElapsed())); + } + + std::vector<std::string> log_; }; HEADLESS_ASYNC_DEVTOOLED_TEST_F(VirtualTimeBrowserTest); diff --git a/chromium/headless/public/headless_browser.cc b/chromium/headless/public/headless_browser.cc index d70edaf62c4..497a8c7721d 100644 --- a/chromium/headless/public/headless_browser.cc +++ b/chromium/headless/public/headless_browser.cc @@ -84,6 +84,11 @@ Builder& Builder::SetUserAgent(const std::string& user_agent) { return *this; } +Builder& Builder::SetAcceptLanguage(const std::string& accept_language) { + options_.accept_language = accept_language; + return *this; +} + Builder& Builder::EnableDevToolsServer(const net::IPEndPoint& endpoint) { options_.devtools_endpoint = endpoint; return *this; diff --git a/chromium/headless/public/headless_browser.h b/chromium/headless/public/headless_browser.h index 63f6c747027..352a22818d5 100644 --- a/chromium/headless/public/headless_browser.h +++ b/chromium/headless/public/headless_browser.h @@ -149,6 +149,7 @@ struct HEADLESS_EXPORT HeadlessBrowser::Options { // Default per-context options, can be specialized on per-context basis. std::string product_name_and_version; + std::string accept_language; std::string user_agent; // The ProxyConfig to use. The system proxy settings are used by default. @@ -215,6 +216,7 @@ class HEADLESS_EXPORT HeadlessBrowser::Options::Builder { Builder& SetProductNameAndVersion( const std::string& product_name_and_version); + Builder& SetAcceptLanguage(const std::string& accept_language); Builder& SetUserAgent(const std::string& user_agent); Builder& SetProxyConfig(std::unique_ptr<net::ProxyConfig> proxy_config); Builder& SetHostResolverRules(const std::string& host_resolver_rules); diff --git a/chromium/headless/public/headless_browser_context.h b/chromium/headless/public/headless_browser_context.h index 0b2a002d8fb..6cb289f29c7 100644 --- a/chromium/headless/public/headless_browser_context.h +++ b/chromium/headless/public/headless_browser_context.h @@ -88,6 +88,9 @@ class HEADLESS_EXPORT HeadlessBrowserContext::Observer { // thread. virtual void UrlRequestFailed(net::URLRequest* request, int net_error) {} + // Indicates the HeadlessBrowserContext is about to be deleted. + virtual void OnHeadlessBrowserContextDestruct() {} + protected: virtual ~Observer() {} }; @@ -120,6 +123,7 @@ class HEADLESS_EXPORT HeadlessBrowserContext::Builder { // settings. See HeadlessBrowser::Options for their meaning. Builder& SetProductNameAndVersion( const std::string& product_name_and_version); + Builder& SetAcceptLanguage(const std::string& accept_language); Builder& SetUserAgent(const std::string& user_agent); Builder& SetProxyConfig(std::unique_ptr<net::ProxyConfig> proxy_config); Builder& SetHostResolverRules(const std::string& host_resolver_rules); diff --git a/chromium/headless/public/headless_web_contents.h b/chromium/headless/public/headless_web_contents.h index dae0d72c515..96259c3a194 100644 --- a/chromium/headless/public/headless_web_contents.h +++ b/chromium/headless/public/headless_web_contents.h @@ -90,8 +90,15 @@ class HEADLESS_EXPORT HeadlessWebContents { int process_id, int frame_tree_node_id) const = 0; + // Returns the FrameTreeNode id corresponding to |devtools_id| or -1 if it + // can't be found. Must be called on the IO thread. + virtual int GetFrameTreeNodeIdForDevToolsFrameId( + const std::string& devtools_id) const = 0; + virtual int GetMainFrameRenderProcessId() const = 0; + virtual int GetMainFrameTreeNodeId() const = 0; + private: friend class HeadlessWebContentsImpl; HeadlessWebContents() {} diff --git a/chromium/headless/public/util/deterministic_http_protocol_handler.cc b/chromium/headless/public/util/deterministic_http_protocol_handler.cc index 6235855973b..8ff8e37df81 100644 --- a/chromium/headless/public/util/deterministic_http_protocol_handler.cc +++ b/chromium/headless/public/util/deterministic_http_protocol_handler.cc @@ -22,11 +22,6 @@ class DeterministicHttpProtocolHandler::NopGenericURLRequestJobDelegate NopGenericURLRequestJobDelegate() {} ~NopGenericURLRequestJobDelegate() override {} - // GenericURLRequestJob::Delegate methods: - void OnPendingRequest(PendingRequest* pending_request) override { - pending_request->AllowRequest(); - } - void OnResourceLoadFailed(const Request* request, net::Error error) override { } diff --git a/chromium/headless/public/util/fontconfig.cc b/chromium/headless/public/util/fontconfig.cc index d859c19063f..2f3cd0b8d7e 100644 --- a/chromium/headless/public/util/fontconfig.cc +++ b/chromium/headless/public/util/fontconfig.cc @@ -70,8 +70,8 @@ void InitFonts(const char* fontconfig_path) { // stable across runs, otherwise replacement font picks are random // and cause flakiness. std::set<std::string> fonts; - struct dirent entry, *result; - while (readdir_r(fc_dir, &entry, &result) == 0 && result != NULL) { + struct dirent *result; + while ((result = readdir(fc_dir))) { fonts.insert(result->d_name); } for (const std::string& font : fonts) { diff --git a/chromium/headless/public/util/generic_url_request_job.cc b/chromium/headless/public/util/generic_url_request_job.cc index 84344322f37..046499ce98c 100644 --- a/chromium/headless/public/util/generic_url_request_job.cc +++ b/chromium/headless/public/util/generic_url_request_job.cc @@ -8,6 +8,8 @@ #include <algorithm> #include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" #include "content/public/browser/devtools_agent_host.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/resource_request_info.h" @@ -22,6 +24,7 @@ #include "net/cookies/cookie_store.h" #include "net/http/http_response_headers.h" #include "net/http/http_util.h" +#include "net/url_request/http_user_agent_settings.h" #include "net/url_request/url_request_context.h" namespace headless { @@ -55,19 +58,35 @@ void GenericURLRequestJob::SetExtraRequestHeaders( const net::HttpRequestHeaders& headers) { DCHECK(origin_task_runner_->RunsTasksInCurrentSequence()); extra_request_headers_ = headers; + + if (!request_->referrer().empty()) { + extra_request_headers_.SetHeader(net::HttpRequestHeaders::kReferer, + request_->referrer()); + } + + const net::HttpUserAgentSettings* user_agent_settings = + request()->context()->http_user_agent_settings(); + if (user_agent_settings) { + // If set the |user_agent_settings| accept language is a fallback. + extra_request_headers_.SetHeaderIfMissing( + net::HttpRequestHeaders::kAcceptLanguage, + user_agent_settings->GetAcceptLanguage()); + // If set the |user_agent_settings| user agent is an override. + if (!user_agent_settings->GetUserAgent().empty()) { + extra_request_headers_.SetHeader(net::HttpRequestHeaders::kUserAgent, + user_agent_settings->GetUserAgent()); + } + } } void GenericURLRequestJob::Start() { PrepareCookies(request_->url(), request_->method(), - url::Origin(request_->first_party_for_cookies()), - base::Bind(&Delegate::OnPendingRequest, - base::Unretained(delegate_), this)); + url::Origin(request_->site_for_cookies())); } void GenericURLRequestJob::PrepareCookies(const GURL& rewritten_url, const std::string& method, - const url::Origin& site_for_cookies, - const base::Closure& done_callback) { + const url::Origin& site_for_cookies) { DCHECK(origin_task_runner_->RunsTasksInCurrentSequence()); net::CookieStore* cookie_store = request_->context()->cookie_store(); net::CookieOptions options; @@ -92,14 +111,12 @@ void GenericURLRequestJob::PrepareCookies(const GURL& rewritten_url, cookie_store->GetCookieListWithOptionsAsync( rewritten_url, options, base::Bind(&GenericURLRequestJob::OnCookiesAvailable, - weak_factory_.GetWeakPtr(), rewritten_url, method, - done_callback)); + weak_factory_.GetWeakPtr(), rewritten_url, method)); } void GenericURLRequestJob::OnCookiesAvailable( const GURL& rewritten_url, const std::string& method, - const base::Closure& done_callback, const net::CookieList& cookie_list) { DCHECK(origin_task_runner_->RunsTasksInCurrentSequence()); // TODO(alexclarke): Set user agent. @@ -108,12 +125,7 @@ void GenericURLRequestJob::OnCookiesAvailable( if (!cookie.empty()) extra_request_headers_.SetHeader(net::HttpRequestHeaders::kCookie, cookie); - if (!request_->referrer().empty()) { - extra_request_headers_.SetHeader(net::HttpRequestHeaders::kReferer, - request_->referrer()); - } - - done_callback.Run(); + url_fetcher_->StartFetch(this, this); } void GenericURLRequestJob::OnFetchStartError(net::Error error) { @@ -126,12 +138,13 @@ void GenericURLRequestJob::OnFetchComplete( const GURL& final_url, scoped_refptr<net::HttpResponseHeaders> response_headers, const char* body, - size_t body_size) { + size_t body_size, + const net::LoadTimingInfo& load_timing_info) { DCHECK(origin_task_runner_->RunsTasksInCurrentSequence()); - response_time_ = base::TimeTicks::Now(); response_headers_ = response_headers; body_ = body; body_size_ = body_size; + load_timing_info_ = load_timing_info; DispatchHeadersComplete(); @@ -155,6 +168,10 @@ int GenericURLRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size) { void GenericURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) { info->headers = response_headers_; + + // Important we need to set this so we can detect if a navigation request got + // canceled by DevTools. + info->network_accessed = true; } bool GenericURLRequestJob::GetMimeType(std::string* mime_type) const { @@ -171,8 +188,7 @@ bool GenericURLRequestJob::GetCharset(std::string* charset) { void GenericURLRequestJob::GetLoadTimingInfo( net::LoadTimingInfo* load_timing_info) const { - // TODO(alexclarke): Investigate setting the other members too where possible. - load_timing_info->receive_headers_end = response_time_; + *load_timing_info = load_timing_info_; } uint64_t GenericURLRequestJob::GenericURLRequestJob::GetRequestId() const { @@ -183,6 +199,11 @@ const net::URLRequest* GenericURLRequestJob::GetURLRequest() const { return request_; } +const net::HttpRequestHeaders& GenericURLRequestJob::GetHttpRequestHeaders() + const { + return extra_request_headers_; +} + int GenericURLRequestJob::GetFrameTreeNodeId() const { // URLRequestUserData will be set for all renderer initiated resource // requests, but not for browser side navigations. @@ -265,6 +286,13 @@ bool GenericURLRequestJob::IsAsync() const { return true; } +namespace { +void CompletionCallback(int* dest, base::Closure* quit_closure, int value) { + *dest = value; + quit_closure->Run(); +} +} // namespace + std::string GenericURLRequestJob::GetPostData() const { if (!request_->has_upload()) return ""; @@ -277,76 +305,80 @@ std::string GenericURLRequestJob::GetPostData() const { return ""; DCHECK_EQ(1u, stream->GetElementReaders()->size()); - const net::UploadBytesElementReader* reader = - (*stream->GetElementReaders())[0]->AsBytesReader(); - if (!reader) - return ""; - return std::string(reader->bytes(), reader->length()); -} - -const Request* GenericURLRequestJob::GetRequest() const { - return this; -} + const std::unique_ptr<net::UploadElementReader>& reader = + (*stream->GetElementReaders())[0]; + // If |reader| is actually an UploadBytesElementReader we can get the data + // directly (should be faster than the horrible stuff below). + const net::UploadBytesElementReader* bytes_reader = reader->AsBytesReader(); + if (bytes_reader) + return std::string(bytes_reader->bytes(), bytes_reader->length()); + + // TODO(alexclarke): Consider changing the interface of + // GenericURLRequestJob::GetPostData to use a callback which would let us + // avoid the nested run loops below. + + // Initialize the reader. + { + base::Closure quit_closure; + int init_result = reader->Init( + base::Bind(&CompletionCallback, &init_result, &quit_closure)); + if (init_result == net::ERR_IO_PENDING) { + base::RunLoop nested_run_loop; + base::MessageLoop::ScopedNestableTaskAllower allow_nested( + base::MessageLoop::current()); + quit_closure = nested_run_loop.QuitClosure(); + nested_run_loop.Run(); + } -void GenericURLRequestJob::AllowRequest() { - if (!origin_task_runner_->RunsTasksInCurrentSequence()) { - origin_task_runner_->PostTask( - FROM_HERE, base::Bind(&GenericURLRequestJob::AllowRequest, - weak_factory_.GetWeakPtr())); - return; + if (init_result != net::OK) + return ""; } - url_fetcher_->StartFetch(request_->url(), request_->method(), GetPostData(), - extra_request_headers_, this); -} - -void GenericURLRequestJob::BlockRequest(net::Error error) { - if (!origin_task_runner_->RunsTasksInCurrentSequence()) { - origin_task_runner_->PostTask( - FROM_HERE, base::Bind(&GenericURLRequestJob::BlockRequest, - weak_factory_.GetWeakPtr(), error)); - return; - } + // Read the POST bytes. + uint64_t content_length = reader->GetContentLength(); + std::string post_data; + post_data.reserve(content_length); + const size_t block_size = 1024; + scoped_refptr<net::IOBuffer> read_buffer(new net::IOBuffer(block_size)); + while (post_data.size() < content_length) { + base::Closure quit_closure; + int bytes_read = reader->Read( + read_buffer.get(), block_size, + base::Bind(&CompletionCallback, &bytes_read, &quit_closure)); + + if (bytes_read == net::ERR_IO_PENDING) { + base::MessageLoop::ScopedNestableTaskAllower allow_nested( + base::MessageLoop::current()); + base::RunLoop nested_run_loop; + quit_closure = nested_run_loop.QuitClosure(); + nested_run_loop.Run(); + } - DispatchStartError(error); -} + // Bail out if an error occured. + if (bytes_read < 0) + return ""; -void GenericURLRequestJob::ModifyRequest( - const GURL& url, - const std::string& method, - const std::string& post_data, - const net::HttpRequestHeaders& request_headers) { - if (!origin_task_runner_->RunsTasksInCurrentSequence()) { - origin_task_runner_->PostTask( - FROM_HERE, base::Bind(&GenericURLRequestJob::ModifyRequest, - weak_factory_.GetWeakPtr(), url, method, - post_data, request_headers)); - return; + post_data.append(read_buffer->data(), bytes_read); } - extra_request_headers_ = request_headers; - PrepareCookies( - request_->url(), request_->method(), - url::Origin(request_->first_party_for_cookies()), - base::Bind(&URLFetcher::StartFetch, base::Unretained(url_fetcher_.get()), - url, method, post_data, request_headers, this)); + return post_data; } -void GenericURLRequestJob::MockResponse( - std::unique_ptr<MockResponseData> mock_response) { - if (!origin_task_runner_->RunsTasksInCurrentSequence()) { - origin_task_runner_->PostTask( - FROM_HERE, base::Bind(&GenericURLRequestJob::MockResponse, - weak_factory_.GetWeakPtr(), - base::Passed(std::move(mock_response)))); - return; - } +uint64_t GenericURLRequestJob::GetPostDataSize() const { + if (!request_->has_upload()) + return 0; - mock_response_ = std::move(mock_response); + const net::UploadDataStream* stream = request_->get_upload(); + if (!stream->GetElementReaders()) + return 0; + + if (stream->GetElementReaders()->size() == 0) + return 0; - OnFetchCompleteExtractHeaders(request_->url(), - mock_response_->response_data.data(), - mock_response_->response_data.size()); + DCHECK_EQ(1u, stream->GetElementReaders()->size()); + const std::unique_ptr<net::UploadElementReader>& reader = + (*stream->GetElementReaders())[0]; + return reader->GetContentLength(); } } // namespace headless diff --git a/chromium/headless/public/util/generic_url_request_job.h b/chromium/headless/public/util/generic_url_request_job.h index 434de842028..c05b452eac7 100644 --- a/chromium/headless/public/util/generic_url_request_job.h +++ b/chromium/headless/public/util/generic_url_request_job.h @@ -42,6 +42,8 @@ class HEADLESS_EXPORT Request { virtual const net::URLRequest* GetURLRequest() const = 0; + virtual const net::HttpRequestHeaders& GetHttpRequestHeaders() const = 0; + // The frame from which the request came from. virtual int GetFrameTreeNodeId() const = 0; @@ -51,6 +53,9 @@ class HEADLESS_EXPORT Request { // Gets the POST data, if any, from the net::URLRequest. virtual std::string GetPostData() const = 0; + // Returns the size of the POST data, if any, from the net::URLRequest. + virtual uint64_t GetPostDataSize() const = 0; + enum class ResourceType { MAIN_FRAME = 0, SUB_FRAME = 1, @@ -86,41 +91,6 @@ class HEADLESS_EXPORT Request { DISALLOW_COPY_AND_ASSIGN(Request); }; -// Details of a pending request received by GenericURLRequestJob which must be -// either Allowed, Blocked, Modified or have it's response Mocked. -class HEADLESS_EXPORT PendingRequest { - public: - virtual const Request* GetRequest() const = 0; - - // Allows the request to proceed as normal. - virtual void AllowRequest() = 0; - - // Causes the request to fail with the specified |error|. - virtual void BlockRequest(net::Error error) = 0; - - // Allows the request to be completely re-written. - virtual void ModifyRequest( - const GURL& url, - const std::string& method, - const std::string& post_data, - const net::HttpRequestHeaders& request_headers) = 0; - - struct MockResponseData { - std::string response_data; - }; - - // Instead of fetching the request, |mock_response| is returned instead. - virtual void MockResponse( - std::unique_ptr<MockResponseData> mock_response) = 0; - - protected: - PendingRequest() {} - virtual ~PendingRequest() {} - - private: - DISALLOW_COPY_AND_ASSIGN(PendingRequest); -}; - // Intended for use in a protocol handler, this ManagedDispatchURLRequestJob has // the following features: // @@ -130,16 +100,10 @@ class HEADLESS_EXPORT PendingRequest { class HEADLESS_EXPORT GenericURLRequestJob : public ManagedDispatchURLRequestJob, public URLFetcher::ResultListener, - public PendingRequest, public Request { public: class HEADLESS_EXPORT Delegate { public: - // Notifies the delegate of an PendingRequest which must either be - // allowed, blocked, modifed or it's response mocked. Called on an arbitrary - // thread. - virtual void OnPendingRequest(PendingRequest* pending_request) = 0; - // Notifies the delegate of any fetch failure. Called on an arbitrary // thread. virtual void OnResourceLoadFailed(const Request* request, @@ -176,55 +140,45 @@ class HEADLESS_EXPORT GenericURLRequestJob bool GetCharset(std::string* charset) override; void GetLoadTimingInfo(net::LoadTimingInfo* load_timing_info) const override; - // URLFetcher::FetchResultListener implementation: + // URLFetcher::ResultListener implementation: void OnFetchStartError(net::Error error) override; void OnFetchComplete(const GURL& final_url, scoped_refptr<net::HttpResponseHeaders> response_headers, const char* body, - size_t body_size) override; + size_t body_size, + const net::LoadTimingInfo& load_timing_info) override; protected: // Request implementation: uint64_t GetRequestId() const override; + const net::HttpRequestHeaders& GetHttpRequestHeaders() const override; const net::URLRequest* GetURLRequest() const override; int GetFrameTreeNodeId() const override; std::string GetDevToolsAgentHostId() const override; std::string GetPostData() const override; + uint64_t GetPostDataSize() const override; ResourceType GetResourceType() const override; bool IsAsync() const override; - // PendingRequest implementation: - const Request* GetRequest() const override; - void AllowRequest() override; - void BlockRequest(net::Error error) override; - void ModifyRequest(const GURL& url, - const std::string& method, - const std::string& post_data, - const net::HttpRequestHeaders& request_headers) override; - void MockResponse(std::unique_ptr<MockResponseData> mock_response) override; - private: void PrepareCookies(const GURL& rewritten_url, const std::string& method, - const url::Origin& site_for_cookies, - const base::Closure& done_callback); + const url::Origin& site_for_cookies); void OnCookiesAvailable(const GURL& rewritten_url, const std::string& method, - const base::Closure& done_callback, const net::CookieList& cookie_list); std::unique_ptr<URLFetcher> url_fetcher_; net::HttpRequestHeaders extra_request_headers_; scoped_refptr<net::HttpResponseHeaders> response_headers_; scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_; - std::unique_ptr<MockResponseData> mock_response_; Delegate* delegate_; // Not owned. HeadlessBrowserContext* headless_browser_context_; // Not owned. const content::ResourceRequestInfo* request_resource_info_; // Not owned. const char* body_ = nullptr; // Not owned. size_t body_size_ = 0; size_t read_offset_ = 0; - base::TimeTicks response_time_; + net::LoadTimingInfo load_timing_info_; const uint64_t request_id_; static uint64_t next_request_id_; diff --git a/chromium/headless/public/util/generic_url_request_job_test.cc b/chromium/headless/public/util/generic_url_request_job_test.cc index faf0bd65845..23b3f34441f 100644 --- a/chromium/headless/public/util/generic_url_request_job_test.cc +++ b/chromium/headless/public/util/generic_url_request_job_test.cc @@ -23,13 +23,14 @@ #include "net/base/upload_bytes_element_reader.h" #include "net/http/http_response_headers.h" #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" +#include "net/url_request/static_http_user_agent_settings.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" using testing::_; -std::ostream& operator<<(std::ostream& os, const base::Value& value) { +std::ostream& operator<<(std::ostream& os, const base::DictionaryValue& value) { std::string json; base::JSONWriter::WriteWithOptions( value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json); @@ -56,29 +57,34 @@ class MockDelegate : public MockGenericURLRequestJobDelegate { class MockFetcher : public URLFetcher { public: MockFetcher(base::DictionaryValue* fetch_request, - std::map<std::string, std::string>* json_fetch_reply_map) + std::map<std::string, std::string>* json_fetch_reply_map, + base::Callback<void(const Request*)>* on_request_callback) : json_fetch_reply_map_(json_fetch_reply_map), - fetch_request_(fetch_request) {} + fetch_request_(fetch_request), + on_request_callback_(on_request_callback) {} ~MockFetcher() override {} - void StartFetch(const GURL& url, - const std::string& method, - const std::string& post_data, - const net::HttpRequestHeaders& request_headers, + void StartFetch(const Request* request, ResultListener* result_listener) override { + if (!on_request_callback_->is_null()) + on_request_callback_->Run(request); + // Record the request. - fetch_request_->SetString("url", url.spec()); - fetch_request_->SetString("method", method); + std::string url = request->GetURLRequest()->url().spec(); + fetch_request_->SetString("url", url); + fetch_request_->SetString("method", request->GetURLRequest()->method()); std::unique_ptr<base::DictionaryValue> headers(new base::DictionaryValue); - for (net::HttpRequestHeaders::Iterator it(request_headers); it.GetNext();) { + for (net::HttpRequestHeaders::Iterator it(request->GetHttpRequestHeaders()); + it.GetNext();) { headers->SetString(it.name(), it.value()); } fetch_request_->Set("headers", std::move(headers)); + std::string post_data = request->GetPostData(); if (!post_data.empty()) - fetch_request_->SetString("post_data", post_data); + fetch_request_->SetString("post_data", std::move(post_data)); - const auto find_it = json_fetch_reply_map_->find(url.spec()); + const auto find_it = json_fetch_reply_map_->find(url); if (find_it == json_fetch_reply_map_->end()) { result_listener->OnFetchStartError(net::ERR_ADDRESS_UNREACHABLE); return; @@ -107,14 +113,20 @@ class MockFetcher : public URLFetcher { base::StringPrintf("%s: %s", it.key().c_str(), value.c_str())); } + // Set the fields needed for tracing, so that we can check + // if they are forwarded correctly. + net::LoadTimingInfo load_timing_info; + load_timing_info.send_start = base::TimeTicks::Max(); + load_timing_info.receive_headers_end = base::TimeTicks::Max(); result_listener->OnFetchComplete( GURL(final_url), std::move(response_headers), response_data_.c_str(), - response_data_.size()); + response_data_.size(), load_timing_info); } private: std::map<std::string, std::string>* json_fetch_reply_map_; // NOT OWNED - base::DictionaryValue* fetch_request_; // NOT OWNED + base::DictionaryValue* fetch_request_; // NOT OWNED + base::Callback<void(const Request*)>* on_request_callback_; // NOT OWNED std::string response_data_; // Here to ensure the required lifetime. }; @@ -125,11 +137,13 @@ class MockProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { MockProtocolHandler(base::DictionaryValue* fetch_request, std::map<std::string, std::string>* json_fetch_reply_map, URLRequestDispatcher* dispatcher, - GenericURLRequestJob::Delegate* job_delegate) + GenericURLRequestJob::Delegate* job_delegate, + base::Callback<void(const Request*)>* on_request_callback) : fetch_request_(fetch_request), json_fetch_reply_map_(json_fetch_reply_map), job_delegate_(job_delegate), - dispatcher_(dispatcher) {} + dispatcher_(dispatcher), + on_request_callback_(on_request_callback) {} // net::URLRequestJobFactory::ProtocolHandler override. net::URLRequestJob* MaybeCreateJob( @@ -137,15 +151,17 @@ class MockProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { net::NetworkDelegate* network_delegate) const override { return new GenericURLRequestJob( request, network_delegate, dispatcher_, - base::MakeUnique<MockFetcher>(fetch_request_, json_fetch_reply_map_), + base::MakeUnique<MockFetcher>(fetch_request_, json_fetch_reply_map_, + on_request_callback_), job_delegate_, nullptr); } private: - base::DictionaryValue* fetch_request_; // NOT OWNED - std::map<std::string, std::string>* json_fetch_reply_map_; // NOT OWNED - GenericURLRequestJob::Delegate* job_delegate_; // NOT OWNED - URLRequestDispatcher* dispatcher_; // NOT OWNED + base::DictionaryValue* fetch_request_; // NOT OWNED + std::map<std::string, std::string>* json_fetch_reply_map_; // NOT OWNED + GenericURLRequestJob::Delegate* job_delegate_; // NOT OWNED + URLRequestDispatcher* dispatcher_; // NOT OWNED + base::Callback<void(const Request*)>* on_request_callback_; // NOT OWNED }; } // namespace @@ -156,7 +172,7 @@ class GenericURLRequestJobTest : public testing::Test { url_request_job_factory_.SetProtocolHandler( "https", base::WrapUnique(new MockProtocolHandler( &fetch_request_, &json_fetch_reply_map_, &dispatcher_, - &job_delegate_))); + &job_delegate_, &on_request_callback_))); url_request_context_.set_job_factory(&url_request_job_factory_); url_request_context_.set_cookie_store(&cookie_store_); } @@ -206,9 +222,12 @@ class GenericURLRequestJobTest : public testing::Test { std::map<std::string, std::string> json_fetch_reply_map_; // Replies to be sent by MockFetcher. MockDelegate job_delegate_; + base::Callback<void(const Request*)> on_request_callback_; }; TEST_F(GenericURLRequestJobTest, BasicGetRequestParams) { + net::StaticHttpUserAgentSettings user_agent_settings("en-UK", "TestBrowser"); + json_fetch_reply_map_["https://example.com/"] = R"( { "url": "https://example.com", @@ -218,13 +237,12 @@ TEST_F(GenericURLRequestJobTest, BasicGetRequestParams) { } })"; + url_request_context_.set_http_user_agent_settings(&user_agent_settings); std::unique_ptr<net::URLRequest> request(url_request_context_.CreateRequest( GURL("https://example.com"), net::DEFAULT_PRIORITY, &request_delegate_, TRAFFIC_ANNOTATION_FOR_TESTS)); 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(); @@ -233,7 +251,7 @@ TEST_F(GenericURLRequestJobTest, BasicGetRequestParams) { "url": "https://example.com/", "method": "GET", "headers": { - "Accept": "text/plain", + "Accept-Language": "en-UK", "Extra-Header": "Value", "Referer": "https://referrer.example.com/", "User-Agent": "TestBrowser" @@ -337,7 +355,10 @@ TEST_F(GenericURLRequestJobTest, BasicRequestContents) { net::LoadTimingInfo load_timing_info; request->GetLoadTimingInfo(&load_timing_info); - EXPECT_FALSE(load_timing_info.receive_headers_end.is_null()); + // Check that the send_start and receive_headers_end timings are + // forwarded correctly, as they are used by tracing. + EXPECT_EQ(base::TimeTicks::Max(), load_timing_info.send_start); + EXPECT_EQ(base::TimeTicks::Max(), load_timing_info.receive_headers_end); } TEST_F(GenericURLRequestJobTest, ReadInParts) { @@ -451,159 +472,6 @@ TEST_F(GenericURLRequestJobTest, RequestWithCookies) { EXPECT_THAT(fetch_request_, MatchesJson(expected_request_json)); } -TEST_F(GenericURLRequestJobTest, DelegateBlocksLoading) { - std::string reply = R"( - { - "url": "https://example.com", - "data": "Reply", - "headers": { - "Content-Type": "text/html; charset=UTF-8" - } - })"; - - job_delegate_.SetPolicy(base::Bind([](PendingRequest* pending_request) { - pending_request->BlockRequest(net::ERR_FILE_NOT_FOUND); - })); - - std::unique_ptr<net::URLRequest> request( - CreateAndCompleteGetJob(GURL("https://example.com"), reply)); - - EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status()); - EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request->status().error()); -} - -TEST_F(GenericURLRequestJobTest, DelegateModifiesRequest) { - json_fetch_reply_map_["https://example.com/"] = R"( - { - "url": "https://example.com", - "data": "Welcome to example.com", - "headers": { - "Content-Type": "text/html; charset=UTF-8" - } - })"; - - json_fetch_reply_map_["https://othersite.com/"] = R"( - { - "url": "https://example.com", - "data": "Welcome to othersite.com", - "headers": { - "Content-Type": "text/html; charset=UTF-8" - } - })"; - - // Turn the GET into a POST to a different site. - job_delegate_.SetPolicy(base::Bind([](PendingRequest* pending_request) { - net::HttpRequestHeaders headers; - headers.SetHeader("TestHeader", "Hello"); - pending_request->ModifyRequest(GURL("https://othersite.com"), "POST", - "Some post data!", headers); - })); - - std::unique_ptr<net::URLRequest> request(url_request_context_.CreateRequest( - GURL("https://example.com"), net::DEFAULT_PRIORITY, &request_delegate_, - TRAFFIC_ANNOTATION_FOR_TESTS)); - request->Start(); - base::RunLoop().RunUntilIdle(); - - std::string expected_request_json = R"( - { - "url": "https://othersite.com/", - "method": "POST", - "post_data": "Some post data!", - "headers": { - "TestHeader": "Hello" - } - })"; - - EXPECT_THAT(fetch_request_, MatchesJson(expected_request_json)); - - EXPECT_EQ(200, request->GetResponseCode()); - // The modification should not be visible to the URlRequest. - EXPECT_EQ("https://example.com/", request->url().spec()); - EXPECT_EQ("GET", request->method()); - - 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(24, bytes_read); - EXPECT_EQ("Welcome to othersite.com", - std::string(buffer->data(), bytes_read)); -} - -TEST_F(GenericURLRequestJobTest, DelegateMocks404Response) { - std::string reply = R"( - { - "url": "https://example.com", - "data": "Reply", - "headers": { - "Content-Type": "text/html; charset=UTF-8" - } - })"; - - job_delegate_.SetPolicy(base::Bind([](PendingRequest* pending_request) { - std::unique_ptr<GenericURLRequestJob::MockResponseData> mock_response_data( - new GenericURLRequestJob::MockResponseData()); - mock_response_data->response_data = "HTTP/1.1 404 Not Found\r\n\r\n"; - pending_request->MockResponse(std::move(mock_response_data)); - })); - - std::unique_ptr<net::URLRequest> request( - CreateAndCompleteGetJob(GURL("https://example.com"), reply)); - - EXPECT_EQ(404, request->GetResponseCode()); -} - -TEST_F(GenericURLRequestJobTest, DelegateMocks302Response) { - job_delegate_.SetPolicy(base::Bind([](PendingRequest* pending_request) { - if (pending_request->GetRequest()->GetURLRequest()->url().spec() == - "https://example.com/") { - std::unique_ptr<GenericURLRequestJob::MockResponseData> - mock_response_data(new GenericURLRequestJob::MockResponseData()); - mock_response_data->response_data = - "HTTP/1.1 302 Found\r\n" - "Location: https://foo.com/\r\n\r\n"; - pending_request->MockResponse(std::move(mock_response_data)); - } else { - pending_request->AllowRequest(); - } - })); - - json_fetch_reply_map_["https://example.com/"] = R"( - { - "url": "https://example.com", - "data": "Welcome to example.com", - "headers": { - "Content-Type": "text/html; charset=UTF-8" - } - })"; - - json_fetch_reply_map_["https://foo.com/"] = R"( - { - "url": "https://example.com", - "data": "Welcome to foo.com", - "headers": { - "Content-Type": "text/html; charset=UTF-8" - } - })"; - - std::unique_ptr<net::URLRequest> request(url_request_context_.CreateRequest( - GURL("https://example.com"), net::DEFAULT_PRIORITY, &request_delegate_, - TRAFFIC_ANNOTATION_FOR_TESTS)); - request->Start(); - base::RunLoop().RunUntilIdle(); - - EXPECT_EQ(200, request->GetResponseCode()); - EXPECT_EQ("https://foo.com/", request->url().spec()); - - 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(18, bytes_read); - EXPECT_EQ("Welcome to foo.com", std::string(buffer->data(), bytes_read)); -} - TEST_F(GenericURLRequestJobTest, OnResourceLoadFailed) { EXPECT_CALL(job_delegate_, OnResourceLoadFailed(_, net::ERR_ADDRESS_UNREACHABLE)); @@ -627,12 +495,11 @@ TEST_F(GenericURLRequestJobTest, RequestsHaveDistinctIds) { })"; std::set<uint64_t> ids; - job_delegate_.SetPolicy(base::Bind( - [](std::set<uint64_t>* ids, PendingRequest* pending_request) { - ids->insert(pending_request->GetRequest()->GetRequestId()); - pending_request->AllowRequest(); + on_request_callback_ = base::Bind( + [](std::set<uint64_t>* ids, const Request* request) { + ids->insert(request->GetRequestId()); }, - &ids)); + &ids); CreateAndCompleteGetJob(GURL("https://example.com"), reply); CreateAndCompleteGetJob(GURL("https://example.com"), reply); @@ -654,16 +521,105 @@ TEST_F(GenericURLRequestJobTest, GetPostData) { })"; std::string post_data; - job_delegate_.SetPolicy(base::Bind( - [](std::string* post_data, PendingRequest* pending_request) { - *post_data = pending_request->GetRequest()->GetPostData(); - pending_request->AllowRequest(); + uint64_t post_data_size; + on_request_callback_ = base::Bind( + [](std::string* post_data, uint64_t* post_data_size, + const Request* request) { + *post_data = request->GetPostData(); + *post_data_size = request->GetPostDataSize(); }, - &post_data)); + &post_data, &post_data_size); CreateAndCompletePostJob(GURL("https://example.com"), "payload", reply); EXPECT_EQ("payload", post_data); + EXPECT_EQ(post_data_size, post_data.size()); +} + +namespace { +class ByteAtATimeUploadElementReader : public net::UploadElementReader { + public: + explicit ByteAtATimeUploadElementReader(const std::string& content) + : content_(content) {} + + // net::UploadElementReader implementation: + int Init(const net::CompletionCallback& callback) override { + offset_ = 0; + return net::OK; + } + + uint64_t GetContentLength() const override { return content_.size(); } + + uint64_t BytesRemaining() const override { return content_.size() - offset_; } + + bool IsInMemory() const override { return false; } + + int Read(net::IOBuffer* buf, + int buf_length, + const net::CompletionCallback& callback) override { + if (!BytesRemaining()) + return net::OK; + + base::MessageLoop::current()->task_runner()->PostTask( + FROM_HERE, base::Bind(&ByteAtATimeUploadElementReader::ReadImpl, + base::Unretained(this), make_scoped_refptr(buf), + buf_length, callback)); + return net::ERR_IO_PENDING; + } + + private: + void ReadImpl(scoped_refptr<net::IOBuffer> buf, + int buf_length, + const net::CompletionCallback callback) { + if (BytesRemaining()) { + *buf->data() = content_[offset_++]; + callback.Run(1u); + } else { + callback.Run(0u); + } + } + + std::string content_; + uint64_t offset_ = 0; +}; +} // namespace + +TEST_F(GenericURLRequestJobTest, GetPostDataAsync) { + std::string json_reply = R"( + { + "url": "https://example.com", + "http_response_code": 200, + "data": "Reply", + "headers": { + "Content-Type": "text/html; charset=UTF-8" + } + })"; + + std::string post_data; + uint64_t post_data_size; + on_request_callback_ = base::Bind( + [](std::string* post_data, uint64_t* post_data_size, + const Request* request) { + *post_data = request->GetPostData(); + *post_data_size = request->GetPostDataSize(); + }, + &post_data, &post_data_size); + + GURL url("https://example.com"); + std::unique_ptr<net::URLRequest> request(url_request_context_.CreateRequest( + url, net::DEFAULT_PRIORITY, &request_delegate_, + TRAFFIC_ANNOTATION_FOR_TESTS)); + request->set_method("POST"); + + json_fetch_reply_map_[url.spec()] = json_reply; + + request->set_upload(net::ElementsUploadDataStream::CreateWithReader( + base::MakeUnique<ByteAtATimeUploadElementReader>("payload"), 0)); + request->Start(); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ("payload", post_data); + EXPECT_EQ(post_data_size, post_data.size()); } } // namespace headless diff --git a/chromium/headless/public/util/http_url_fetcher.cc b/chromium/headless/public/util/http_url_fetcher.cc index 1781f3b7ae6..20e44ea93de 100644 --- a/chromium/headless/public/util/http_url_fetcher.cc +++ b/chromium/headless/public/util/http_url_fetcher.cc @@ -4,6 +4,7 @@ #include "headless/public/util/http_url_fetcher.h" +#include "headless/public/util/generic_url_request_job.h" #include "net/base/elements_upload_data_stream.h" #include "net/base/io_buffer.h" #include "net/base/upload_bytes_element_reader.h" @@ -33,7 +34,7 @@ constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation = destination: OTHER } policy { - cookies_allowed: true + cookies_allowed: YES cookies_store: "Various, but cookies stores are deleted when session ends." setting: @@ -211,12 +212,16 @@ void HttpURLFetcher::Delegate::OnResponseCompleted(net::URLRequest* request, return; } + // Extract LoadTimingInfo from the request to pass to the result listener. + net::LoadTimingInfo load_timing_info; + request->GetLoadTimingInfo(&load_timing_info); + // TODO(alexclarke) apart from the headers there's a lot of stuff in // |request->response_info()| that we drop here. Find a way to pipe it // through. result_listener_->OnFetchComplete( request->url(), request->response_info().headers, - bytes_read_so_far_.c_str(), bytes_read_so_far_.size()); + bytes_read_so_far_.c_str(), bytes_read_so_far_.size(), load_timing_info); } HttpURLFetcher::HttpURLFetcher( @@ -225,14 +230,12 @@ HttpURLFetcher::HttpURLFetcher( HttpURLFetcher::~HttpURLFetcher() {} -void HttpURLFetcher::StartFetch(const GURL& rewritten_url, - const std::string& method, - const std::string& post_data, - const net::HttpRequestHeaders& request_headers, +void HttpURLFetcher::StartFetch(const Request* request, ResultListener* result_listener) { - delegate_.reset(new Delegate(rewritten_url, method, post_data, - request_headers, url_request_context_, - result_listener)); + delegate_.reset(new Delegate( + request->GetURLRequest()->url(), request->GetURLRequest()->method(), + request->GetPostData(), request->GetHttpRequestHeaders(), + 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 index c69d9fb201e..70bb710ba5d 100644 --- a/chromium/headless/public/util/http_url_fetcher.h +++ b/chromium/headless/public/util/http_url_fetcher.h @@ -22,10 +22,7 @@ class HttpURLFetcher : public URLFetcher { ~HttpURLFetcher() override; // URLFetcher implementation: - void StartFetch(const GURL& rewritten_url, - const std::string& method, - const std::string& post_data, - const net::HttpRequestHeaders& request_headers, + void StartFetch(const Request* request, ResultListener* result_listener) override; private: diff --git a/chromium/headless/public/util/navigation_request.h b/chromium/headless/public/util/navigation_request.h index cd28f10dd93..06e017f5e8b 100644 --- a/chromium/headless/public/util/navigation_request.h +++ b/chromium/headless/public/util/navigation_request.h @@ -10,8 +10,7 @@ namespace headless { // While the actual details of the navigation processing are left undefined, -// it's anticipated implementations will use devtools Page.setControlNavigations -// and Page.processNavigation commands. +// it's anticipated implementations will use Network.requestIntercepted event. class NavigationRequest { public: NavigationRequest() {} diff --git a/chromium/headless/public/util/testing/generic_url_request_mocks.cc b/chromium/headless/public/util/testing/generic_url_request_mocks.cc index 844945c0a98..c811651ec15 100644 --- a/chromium/headless/public/util/testing/generic_url_request_mocks.cc +++ b/chromium/headless/public/util/testing/generic_url_request_mocks.cc @@ -22,28 +22,6 @@ MockGenericURLRequestJobDelegate::MockGenericURLRequestJobDelegate() MockGenericURLRequestJobDelegate::~MockGenericURLRequestJobDelegate() {} -// GenericURLRequestJob::Delegate methods: -void MockGenericURLRequestJobDelegate::OnPendingRequest( - PendingRequest* pending_request) { - // Simulate the client acknowledging the callback from a different thread. - main_thread_task_runner_->PostTask( - FROM_HERE, base::Bind(&MockGenericURLRequestJobDelegate::ApplyPolicy, - base::Unretained(this), pending_request)); -} - -void MockGenericURLRequestJobDelegate::SetPolicy(Policy policy) { - policy_ = std::move(policy); -} - -void MockGenericURLRequestJobDelegate::ApplyPolicy( - PendingRequest* pending_request) { - if (policy_.is_null()) { - pending_request->AllowRequest(); - } else { - policy_.Run(pending_request); - } -} - void MockGenericURLRequestJobDelegate::OnResourceLoadFailed( const Request* request, net::Error error) {} @@ -159,6 +137,13 @@ MockCookieStore::AddCallbackForCookie(const GURL& url, return nullptr; } +std::unique_ptr<net::CookieStore::CookieChangedSubscription> +MockCookieStore::AddCallbackForAllChanges( + const CookieChangedCallback& callback) { + CHECK(false); + return nullptr; +} + bool MockCookieStore::IsEphemeral() { CHECK(false); return true; diff --git a/chromium/headless/public/util/testing/generic_url_request_mocks.h b/chromium/headless/public/util/testing/generic_url_request_mocks.h index 96b49651deb..565a138a02d 100644 --- a/chromium/headless/public/util/testing/generic_url_request_mocks.h +++ b/chromium/headless/public/util/testing/generic_url_request_mocks.h @@ -26,7 +26,6 @@ class HEADLESS_EXPORT MockGenericURLRequestJobDelegate ~MockGenericURLRequestJobDelegate() override; // GenericURLRequestJob::Delegate methods: - void OnPendingRequest(PendingRequest* pending_request) override; void OnResourceLoadFailed(const Request* request, net::Error error) override; void OnResourceLoadComplete( const Request* request, @@ -35,14 +34,7 @@ class HEADLESS_EXPORT MockGenericURLRequestJobDelegate const char* body, size_t body_size) override; - using Policy = base::Callback<void(PendingRequest* pending_request)>; - - void SetPolicy(Policy policy); - private: - void ApplyPolicy(PendingRequest* pending_request); - - Policy policy_; scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_; DISALLOW_COPY_AND_ASSIGN(MockGenericURLRequestJobDelegate); @@ -117,6 +109,9 @@ class HEADLESS_EXPORT MockCookieStore : public net::CookieStore { const std::string& name, const CookieChangedCallback& callback) override; + std::unique_ptr<CookieChangedSubscription> AddCallbackForAllChanges( + const CookieChangedCallback& callback) override; + bool IsEphemeral() override; net::CookieList* cookies() { return &cookies_; } diff --git a/chromium/headless/public/util/url_fetcher.cc b/chromium/headless/public/util/url_fetcher.cc index 70256e6ae90..c9a79f93e5a 100644 --- a/chromium/headless/public/util/url_fetcher.cc +++ b/chromium/headless/public/util/url_fetcher.cc @@ -15,7 +15,8 @@ namespace headless { void URLFetcher::ResultListener::OnFetchCompleteExtractHeaders( const GURL& final_url, const char* response_data, - size_t response_data_size) { + size_t response_data_size, + const net::LoadTimingInfo& load_timing_info) { size_t read_offset = 0; int header_size = net::HttpUtil::LocateEndOfHeaders(response_data, response_data_size); @@ -32,8 +33,8 @@ void URLFetcher::ResultListener::OnFetchCompleteExtractHeaders( CHECK_LE(read_offset, response_data_size); OnFetchComplete(final_url, std::move(response_headers), - response_data + read_offset, - response_data_size - read_offset); + response_data + read_offset, response_data_size - read_offset, + load_timing_info); } } // namespace headless diff --git a/chromium/headless/public/util/url_fetcher.h b/chromium/headless/public/util/url_fetcher.h index 9dfc08a6154..7dd7558b84f 100644 --- a/chromium/headless/public/util/url_fetcher.h +++ b/chromium/headless/public/util/url_fetcher.h @@ -12,15 +12,16 @@ #include "base/macros.h" #include "base/memory/ref_counted.h" #include "headless/public/headless_export.h" +#include "net/base/load_timing_info.h" #include "net/base/net_errors.h" #include "url/gurl.h" namespace net { -class HttpRequestHeaders; class HttpResponseHeaders; } // namespace net namespace headless { +class Request; // An interface for fetching URLs. Note these are only intended to be used once. class HEADLESS_EXPORT URLFetcher { @@ -43,13 +44,16 @@ class HEADLESS_EXPORT URLFetcher { const GURL& final_url, scoped_refptr<net::HttpResponseHeaders> response_headers, const char* body, - size_t body_size) = 0; + size_t body_size, + const net::LoadTimingInfo& load_timing_info) = 0; // Helper function which extracts the headers from |response_data| and calls // OnFetchComplete. - void OnFetchCompleteExtractHeaders(const GURL& final_url, - const char* response_data, - size_t response_data_size); + void OnFetchCompleteExtractHeaders( + const GURL& final_url, + const char* response_data, + size_t response_data_size, + const net::LoadTimingInfo& load_timing_info); protected: virtual ~ResultListener() {} @@ -58,10 +62,7 @@ class HEADLESS_EXPORT URLFetcher { DISALLOW_COPY_AND_ASSIGN(ResultListener); }; - virtual void StartFetch(const GURL& url, - const std::string& method, - const std::string& post_data, - const net::HttpRequestHeaders& request_headers, + virtual void StartFetch(const Request* request, ResultListener* result_listener) = 0; private: |