diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-01-29 16:35:13 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-02-01 15:33:35 +0000 |
commit | c8c2d1901aec01e934adf561a9fdf0cc776cdef8 (patch) | |
tree | 9157c3d9815e5870799e070b113813bec53e0535 /chromium/headless | |
parent | abefd5095b41dac94ca451d784ab6e27372e981a (diff) | |
download | qtwebengine-chromium-c8c2d1901aec01e934adf561a9fdf0cc776cdef8.tar.gz |
BASELINE: Update Chromium to 64.0.3282.139
Change-Id: I1cae68fe9c94ff7608b26b8382fc19862cdb293a
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/headless')
93 files changed, 3481 insertions, 834 deletions
diff --git a/chromium/headless/BUILD.gn b/chromium/headless/BUILD.gn index e93546c4b99..912c913d763 100644 --- a/chromium/headless/BUILD.gn +++ b/chromium/headless/BUILD.gn @@ -39,6 +39,7 @@ repack("pak") { "$root_gen_dir/content/browser/tracing/tracing_resources.pak", "$root_gen_dir/content/content_resources.pak", "$root_gen_dir/headless/headless_lib_resources.pak", + "$root_gen_dir/mojo/public/js/mojo_bindings_resources.pak", "$root_gen_dir/net/net_resources.pak", "$root_gen_dir/ui/resources/ui_resources_100_percent.pak", "$root_gen_dir/ui/resources/webui_resources.pak", @@ -61,6 +62,7 @@ repack("pak") { "//content/app/strings", "//content/browser/devtools:resources", "//content/browser/tracing:resources", + "//mojo/public/js:resources", "//net:net_resources", "//third_party/WebKit/public:resources", "//third_party/WebKit/public:scaled_resources_100_percent", @@ -172,6 +174,7 @@ devtools_domains = [ "memory", "network", "page", + "performance", "profiler", "runtime", "security", @@ -308,6 +311,8 @@ component("headless") { "public/internal/value_conversions.h", "public/util/black_hole_protocol_handler.cc", "public/util/black_hole_protocol_handler.h", + "public/util/compositor_controller.cc", + "public/util/compositor_controller.h", "public/util/deterministic_dispatcher.cc", "public/util/deterministic_dispatcher.h", "public/util/deterministic_http_protocol_handler.cc", @@ -385,6 +390,7 @@ component("headless") { ":headless_render_frame_controller", ":tab_socket", ":version_header", + "//components/cookie_config", "//components/security_state/core", "//content/public/common", "//content/public/common:service_names", @@ -396,6 +402,10 @@ component("headless") { "//url", ] + if (is_linux && !is_chromeos) { + deps += [ "//components/os_crypt" ] + } + if (is_component_build) { sources += [ "lib/browser/headless_content_browser_client.cc", @@ -415,6 +425,7 @@ component("headless") { ] deps += [ + "//components/crash/core/common:crash_key", "//components/security_state/content", "//gin", "//third_party/WebKit/public:blink", @@ -485,6 +496,7 @@ if (!is_component_build) { deps = [ ":headless", + "//components/crash/core/common:crash_key", "//third_party/WebKit/public:blink_headers", "//ui/base", "//v8", @@ -541,6 +553,7 @@ group("headless_tests") { test("headless_unittests") { sources = [ "public/domains/types_unittest.cc", + "public/util/compositor_controller_unittest.cc", "public/util/deterministic_dispatcher_test.cc", "public/util/error_reporter_unittest.cc", "public/util/expedited_dispatcher_test.cc", @@ -651,7 +664,7 @@ if (is_linux) { ] grit_flags = [ "-E", - "gen_root=" + rebase_path(root_gen_dir), + "gen_root=" + rebase_path(root_gen_dir, root_build_dir), ] deps = [ ":js_devtools_bindings_test", @@ -678,6 +691,7 @@ test("headless_browsertests") { "lib/headless_devtools_client_browsertest.cc", "lib/headless_web_contents_browsertest.cc", "lib/virtual_time_browsertest.cc", + "public/util/compositor_controller_browsertest.cc", "public/util/testing/fake_managed_dispatch_url_request_job.cc", "public/util/testing/fake_managed_dispatch_url_request_job.h", "public/util/testing/generic_url_request_mocks.cc", @@ -686,6 +700,9 @@ test("headless_browsertests") { "public/util/testing/test_in_memory_protocol_handler.h", "test/headless_browser_test.cc", "test/headless_browser_test.h", + "test/headless_render_browsertest.cc", + "test/headless_render_test.cc", + "test/headless_render_test.h", "test/headless_test_launcher.cc", "test/tab_socket_test.cc", "test/tab_socket_test.h", @@ -789,7 +806,10 @@ if (is_win) { "lib/headless_content_main_delegate.h", "lib/headless_content_main_delegate_win.cc", ] - deps += [ "//third_party/WebKit/public:blink_headers" ] + deps += [ + "//components/crash/core/common:crash_key", + "//third_party/WebKit/public:blink_headers", + ] } configs += [ ":headless_implementation" ] @@ -826,7 +846,10 @@ if (is_win) { "lib/utility/headless_content_utility_client.cc", "lib/utility/headless_content_utility_client.h", ] - deps += [ "//third_party/WebKit/public:blink_headers" ] + deps += [ + "//components/crash/core/common:crash_key", + "//third_party/WebKit/public:blink_headers", + ] } configs += [ ":headless_implementation" ] diff --git a/chromium/headless/DEPS b/chromium/headless/DEPS index 343e456ad58..5a088fd8c27 100644 --- a/chromium/headless/DEPS +++ b/chromium/headless/DEPS @@ -1,6 +1,9 @@ include_rules = [ + "+components/cookie_config", "+components/crash/content/app", "+components/crash/content/browser", + "+components/crash/core/common/crash_key.h", + "+components/os_crypt", "+components/printing/service/public/cpp", "+components/printing/service/public/interfaces", "+content/public/app", diff --git a/chromium/headless/app/headless_example.cc b/chromium/headless/app/headless_example.cc index 693095a151f..8ed88a44071 100644 --- a/chromium/headless/app/headless_example.cc +++ b/chromium/headless/app/headless_example.cc @@ -107,13 +107,12 @@ void HeadlessExample::OnLoadEventFired( void HeadlessExample::OnDomFetched( std::unique_ptr<headless::runtime::EvaluateResult> result) { - std::string dom; // Make sure the evaluation succeeded before reading the result. if (result->HasExceptionDetails()) { LOG(ERROR) << "Failed to serialize document: " << result->GetExceptionDetails()->GetText(); - } else if (result->GetResult()->GetValue()->GetAsString(&dom)) { - printf("%s\n", dom.c_str()); + } else { + printf("%s\n", result->GetResult()->GetValue()->GetString().c_str()); } // Shut down the browser (see ~HeadlessExample). diff --git a/chromium/headless/app/headless_shell.cc b/chromium/headless/app/headless_shell.cc index 215bd7005a9..1c6537d152c 100644 --- a/chromium/headless/app/headless_shell.cc +++ b/chromium/headless/app/headless_shell.cc @@ -61,7 +61,8 @@ const char kDefaultScreenshotFileName[] = "screenshot.png"; // Default file name for pdf. Can be overriden by "--print-to-pdf" switch. const char kDefaultPDFFileName[] = "output.pdf"; -bool ParseWindowSize(std::string window_size, gfx::Size* parsed_window_size) { +bool ParseWindowSize(const std::string& window_size, + gfx::Size* parsed_window_size) { int width, height = 0; if (sscanf(window_size.c_str(), "%d%*[x,]%d", &width, &height) >= 2 && width >= 0 && height >= 0) { @@ -128,7 +129,7 @@ HeadlessShell::HeadlessShell() weak_factory_(this) { } -HeadlessShell::~HeadlessShell() {} +HeadlessShell::~HeadlessShell() = default; #if !defined(CHROME_MULTIPLE_DLL_CHILD) void HeadlessShell::OnStart(HeadlessBrowser* browser) { @@ -154,8 +155,8 @@ void HeadlessShell::OnStart(HeadlessBrowser* browser) { DeterministicHttpProtocolHandler* https_handler = nullptr; if (base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kDeterministicFetch)) { - deterministic_dispatcher_.reset( - new DeterministicDispatcher(browser_->BrowserIOThread())); + deterministic_dispatcher_ = + std::make_unique<DeterministicDispatcher>(browser_->BrowserIOThread()); ProtocolHandlerMap protocol_handlers; protocol_handlers[url::kHttpScheme] = @@ -249,12 +250,14 @@ void HeadlessShell::DevToolsTargetReady() { if (base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kDeterministicFetch)) { devtools_client_->GetNetwork()->GetExperimental()->AddObserver(this); - devtools_client_->GetNetwork() - ->GetExperimental() - ->SetRequestInterceptionEnabled( - network::SetRequestInterceptionEnabledParams::Builder() - .SetEnabled(true) - .Build()); + std::unique_ptr<headless::network::RequestPattern> match_all = + headless::network::RequestPattern::Builder().SetUrlPattern("*").Build(); + std::vector<std::unique_ptr<headless::network::RequestPattern>> patterns; + patterns.push_back(std::move(match_all)); + devtools_client_->GetNetwork()->GetExperimental()->SetRequestInterception( + network::SetRequestInterceptionParams::Builder() + .SetPatterns(std::move(patterns)) + .Build()); } if (base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kDefaultBackgroundColor)) { @@ -339,9 +342,8 @@ void HeadlessShell::PollReadyState() { void HeadlessShell::OnReadyState( std::unique_ptr<runtime::EvaluateResult> result) { - std::string ready_state_and_url; - if (result->GetResult()->GetValue()->GetAsString(&ready_state_and_url)) { - std::stringstream stream(ready_state_and_url); + if (result->GetResult()->GetValue()->is_string()) { + std::stringstream stream(result->GetResult()->GetValue()->GetString()); std::string ready_state; std::string url; stream >> ready_state; @@ -424,10 +426,7 @@ void HeadlessShell::OnDomFetched( LOG(ERROR) << "Failed to serialize document: " << result->GetExceptionDetails()->GetText(); } else { - std::string dom; - if (result->GetResult()->GetValue()->GetAsString(&dom)) { - printf("%s\n", dom.c_str()); - } + printf("%s\n", result->GetResult()->GetValue()->GetString().c_str()); } Shutdown(); } @@ -506,23 +505,31 @@ 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); if (file_name.empty()) file_name = base::FilePath().AppendASCII(default_file_name); + std::string decoded_data; + if (!base::Base64Decode(base64_data, &decoded_data)) { + LOG(ERROR) << "Failed to decode base64 data"; + OnFileOpened(std::string(), file_name, base::File::FILE_ERROR_FAILED); + return; + } + file_proxy_ = base::MakeUnique<base::FileProxy>(file_task_runner_.get()); if (!file_proxy_->CreateOrOpen( file_name, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE, base::Bind(&HeadlessShell::OnFileOpened, weak_factory_.GetWeakPtr(), - base64_data, file_name))) { + decoded_data, file_name))) { // Operation could not be started. OnFileOpened(std::string(), file_name, base::File::FILE_ERROR_FAILED); } } -void HeadlessShell::OnFileOpened(const std::string& base64_data, +void HeadlessShell::OnFileOpened(const std::string& decoded_data, const base::FilePath file_name, base::File::Error error_code) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); @@ -533,16 +540,7 @@ void HeadlessShell::OnFileOpened(const std::string& base64_data, return; } - std::string decoded_data; - if (!base::Base64Decode(base64_data, &decoded_data)) { - LOG(ERROR) << "Failed to decode base64 data"; - OnFileWritten(file_name, base64_data.size(), base::File::FILE_ERROR_FAILED, - 0); - return; - } - - scoped_refptr<net::IOBufferWithSize> buf = - new net::IOBufferWithSize(decoded_data.size()); + auto buf = base::MakeRefCounted<net::IOBufferWithSize>(decoded_data.size()); memcpy(buf->data(), decoded_data.data(), decoded_data.size()); if (!file_proxy_->Write( @@ -734,7 +732,7 @@ int HeadlessShellMain(int argc, const char** argv) { if (command_line.HasSwitch(switches::kProxyServer)) { std::string proxy_server = command_line.GetSwitchValueASCII(switches::kProxyServer); - std::unique_ptr<net::ProxyConfig> proxy_config(new net::ProxyConfig); + auto proxy_config = std::make_unique<net::ProxyConfig>(); proxy_config->proxy_rules().ParseFromString(proxy_server); if (command_line.HasSwitch(switches::kProxyBypassList)) { std::string bypass_list = diff --git a/chromium/headless/app/headless_shell.h b/chromium/headless/app/headless_shell.h index 58803aec48e..1b33c333824 100644 --- a/chromium/headless/app/headless_shell.h +++ b/chromium/headless/app/headless_shell.h @@ -90,8 +90,8 @@ class HeadlessShell : public HeadlessWebContents::Observer, void WriteFile(const std::string& switch_string, const std::string& default_file_name, - const std::string& data); - void OnFileOpened(const std::string& data, + const std::string& base64_data); + void OnFileOpened(const std::string& decoded_data, const base::FilePath file_name, base::File::Error error_code); void OnFileWritten(const base::FilePath file_name, diff --git a/chromium/headless/app/headless_shell_switches.cc b/chromium/headless/app/headless_shell_switches.cc index c98f7954e5b..e2cabfa3bcf 100644 --- a/chromium/headless/app/headless_shell_switches.cc +++ b/chromium/headless/app/headless_shell_switches.cc @@ -33,6 +33,16 @@ const char kDumpDom[] = "dump-dom"; // Hide scrollbars from screenshots. const char kHideScrollbars[] = "hide-scrollbars"; +// Specifies which encryption storage backend to use. Possible values are +// kwallet, kwallet5, gnome, gnome-keyring, gnome-libsecret, basic. Any other +// value will lead to Chrome detecting the best backend automatically. +// TODO(crbug.com/571003): Once PasswordStore no longer uses the Keyring or +// KWallet for storing passwords, rename this flag to stop referencing +// passwords. Do not rename it sooner, though; developers and testers might +// rely on it keeping large amounts of testing passwords out of their Keyrings +// or KWallets. +const char kPasswordStore[] = "password-store"; + // Save a pdf file of the loaded page. const char kPrintToPDF[] = "print-to-pdf"; diff --git a/chromium/headless/app/headless_shell_switches.h b/chromium/headless/app/headless_shell_switches.h index 2136c7cfea3..6011f3c22d0 100644 --- a/chromium/headless/app/headless_shell_switches.h +++ b/chromium/headless/app/headless_shell_switches.h @@ -17,6 +17,7 @@ extern const char kDisableCrashReporter[]; extern const char kDumpDom[]; extern const char kEnableCrashReporter[]; extern const char kHideScrollbars[]; +extern const char kPasswordStore[]; extern const char kPrintToPDF[]; extern const char kProxyBypassList[]; extern const char kProxyServer[]; diff --git a/chromium/headless/app/shell_navigation_request.cc b/chromium/headless/app/shell_navigation_request.cc index dc5d7c702bd..3f5344d7d02 100644 --- a/chromium/headless/app/shell_navigation_request.cc +++ b/chromium/headless/app/shell_navigation_request.cc @@ -18,7 +18,7 @@ ShellNavigationRequest::ShellNavigationRequest( DCHECK_CURRENTLY_ON(content::BrowserThread::UI); } -ShellNavigationRequest::~ShellNavigationRequest() {} +ShellNavigationRequest::~ShellNavigationRequest() = default; void ShellNavigationRequest::StartProcessing(base::Closure done_callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); diff --git a/chromium/headless/lib/browser/devtools_api/domain_cc.template b/chromium/headless/lib/browser/devtools_api/domain_cc.template index de9425c33c8..ac16d62dd52 100644 --- a/chromium/headless/lib/browser/devtools_api/domain_cc.template +++ b/chromium/headless/lib/browser/devtools_api/domain_cc.template @@ -110,7 +110,7 @@ void Domain::Handle{{method_name}}Response(base::Callback<void(std::unique_ptr<{ } ErrorReporter errors; std::unique_ptr<{{method_name}}Result> result = {{method_name}}Result::Parse(response, &errors); - DCHECK(!errors.HasErrors()); + DCHECK(!errors.HasErrors()) << errors.ToString(); callback.Run(std::move(result)); } {% endfor %} @@ -121,7 +121,7 @@ void Domain::Handle{{method_name}}Response(base::Callback<void(std::unique_ptr<{ void Domain::Dispatch{{event.name | to_title_case}}Event(const base::Value& params) { ErrorReporter errors; std::unique_ptr<{{event.name | to_title_case}}Params> parsed_params({{event.name | to_title_case}}Params::Parse(params, &errors)); - DCHECK(!errors.HasErrors()); + DCHECK(!errors.HasErrors()) << errors.ToString(); for (ExperimentalObserver& observer : observers_) { observer.On{{event.name | to_title_case}}(*parsed_params); } diff --git a/chromium/headless/lib/browser/devtools_api/domain_type_conversions_h.template b/chromium/headless/lib/browser/devtools_api/domain_type_conversions_h.template index 15796eb756d..eb8297050ce 100644 --- a/chromium/headless/lib/browser/devtools_api/domain_type_conversions_h.template +++ b/chromium/headless/lib/browser/devtools_api/domain_type_conversions_h.template @@ -20,14 +20,13 @@ template <> struct FromValue<{{namespace}}::{{type.id}}> { static {{namespace}}::{{type.id}} Parse(const base::Value& value, ErrorReporter* errors) { {% set default = namespace + '::' + type.id + '::' + type.enum[0] | sanitize_literal | dash_to_camelcase | camelcase_to_hacker_style | upper %} - std::string string_value; - if (!value.GetAsString(&string_value)) { + if (!value.is_string()) { errors->AddError("string enum value expected"); {# Return an arbitrary enum member -- the caller will just ignore it. #} return {{default}}; } {% for literal in type.enum %} - if (string_value == "{{literal}}") + if (value.GetString() == "{{literal}}") return {{namespace}}::{{type.id}}::{{literal | sanitize_literal | dash_to_camelcase | camelcase_to_hacker_style | upper }}; {% endfor %} errors->AddError("invalid enum value"); diff --git a/chromium/headless/lib/browser/devtools_api/domain_types_cc.template b/chromium/headless/lib/browser/devtools_api/domain_types_cc.template index bd1c2773dd4..be125aeb835 100644 --- a/chromium/headless/lib/browser/devtools_api/domain_types_cc.template +++ b/chromium/headless/lib/browser/devtools_api/domain_types_cc.template @@ -31,8 +31,7 @@ namespace {{domain.domain | camelcase_to_hacker_style}} { std::unique_ptr<{{type.id}}> {{type.id}}::Parse(const base::Value& value, ErrorReporter* errors) { errors->Push(); errors->SetName("{{type.id}}"); - const base::DictionaryValue* object; - if (!value.GetAsDictionary(&object)) { + if (!value.is_dict()) { errors->AddError("object expected"); errors->Pop(); return nullptr; @@ -43,8 +42,8 @@ std::unique_ptr<{{type.id}}> {{type.id}}::Parse(const base::Value& value, ErrorR errors->SetName("{{type.id}}"); {% for property in type.properties %} {% set value_name = property.name | camelcase_to_hacker_style + "_value" %} - const base::Value* {{value_name}}; - if (object->Get("{{property.name}}", &{{value_name}})) { + const base::Value* {{value_name}} = value.FindKey("{{property.name}}"); + if ({{value_name}}) { errors->SetName("{{property.name}}"); {% if property.optional %} result->{{property.name | camelcase_to_hacker_style}}_ = internal::FromValue<{{resolve_type(property).raw_type}}>::Parse(*{{value_name}}, errors); diff --git a/chromium/headless/lib/browser/headless_browser_context_impl.cc b/chromium/headless/lib/browser/headless_browser_context_impl.cc index 781c0abe9e3..17849e27819 100644 --- a/chromium/headless/lib/browser/headless_browser_context_impl.cc +++ b/chromium/headless/lib/browser/headless_browser_context_impl.cc @@ -83,9 +83,9 @@ HeadlessBrowserContextImpl::HeadlessBrowserContextImpl( std::unique_ptr<HeadlessBrowserContextOptions> context_options) : browser_(browser), context_options_(std::move(context_options)), - resource_context_(new HeadlessResourceContext), + resource_context_(std::make_unique<HeadlessResourceContext>()), should_remove_headers_(true), - permission_manager_(new HeadlessPermissionManager()), + permission_manager_(std::make_unique<HeadlessPermissionManager>(this)), id_(base::GenerateGUID()) { InitWhileIOAllowed(); } @@ -201,7 +201,8 @@ void HeadlessBrowserContextImpl::Close() { void HeadlessBrowserContextImpl::InitWhileIOAllowed() { if (!context_options_->user_data_dir().empty()) { - path_ = context_options_->user_data_dir(); + path_ = + context_options_->user_data_dir().Append(FILE_PATH_LITERAL("Default")); } else { PathService::Get(base::DIR_EXE, &path_); } @@ -253,8 +254,6 @@ HeadlessBrowserContextImpl::GetSSLHostStateDelegate() { } content::PermissionManager* HeadlessBrowserContextImpl::GetPermissionManager() { - if (!permission_manager_.get()) - permission_manager_.reset(new HeadlessPermissionManager()); return permission_manager_.get(); } @@ -476,6 +475,12 @@ HeadlessBrowserContext::Builder::SetIncognitoMode(bool incognito_mode) { } HeadlessBrowserContext::Builder& +HeadlessBrowserContext::Builder::SetAllowCookies(bool allow_cookies) { + options_->allow_cookies_ = allow_cookies; + return *this; +} + +HeadlessBrowserContext::Builder& HeadlessBrowserContext::Builder::AddTabSocketMojoBindings() { std::string js_bindings = ui::ResourceBundle::GetSharedInstance() @@ -514,13 +519,13 @@ HeadlessBrowserContext* HeadlessBrowserContext::Builder::Build() { return browser_->CreateBrowserContext(this); } -HeadlessBrowserContext::Builder::MojoBindings::MojoBindings() {} +HeadlessBrowserContext::Builder::MojoBindings::MojoBindings() = default; HeadlessBrowserContext::Builder::MojoBindings::MojoBindings( const std::string& mojom_name, const std::string& js_bindings) : mojom_name(mojom_name), js_bindings(js_bindings) {} -HeadlessBrowserContext::Builder::MojoBindings::~MojoBindings() {} +HeadlessBrowserContext::Builder::MojoBindings::~MojoBindings() = default; } // namespace headless diff --git a/chromium/headless/lib/browser/headless_browser_context_impl.h b/chromium/headless/lib/browser/headless_browser_context_impl.h index 580b8b0e15f..d84727c1a5d 100644 --- a/chromium/headless/lib/browser/headless_browser_context_impl.h +++ b/chromium/headless/lib/browser/headless_browser_context_impl.h @@ -13,7 +13,6 @@ #include "base/files/file_path.h" #include "base/unguessable_token.h" #include "content/public/browser/browser_context.h" -#include "content/public/browser/content_browser_client.h" #include "content/public/browser/resource_context.h" #include "headless/lib/browser/headless_browser_context_options.h" #include "headless/lib/browser/headless_network_conditions.h" @@ -120,7 +119,7 @@ class HeadlessBrowserContextImpl : public HeadlessBrowserContext, bool canceled_by_devtools); void SetNetworkConditions(HeadlessNetworkConditions conditions); - HeadlessNetworkConditions GetNetworkConditions(); + HeadlessNetworkConditions GetNetworkConditions() override; private: HeadlessBrowserContextImpl( diff --git a/chromium/headless/lib/browser/headless_browser_context_options.cc b/chromium/headless/lib/browser/headless_browser_context_options.cc index 5898576830a..93e078083b7 100644 --- a/chromium/headless/lib/browser/headless_browser_context_options.cc +++ b/chromium/headless/lib/browser/headless_browser_context_options.cc @@ -74,6 +74,10 @@ bool HeadlessBrowserContextOptions::incognito_mode() const { browser_options_->incognito_mode); } +bool HeadlessBrowserContextOptions::allow_cookies() const { + return ReturnOverriddenValue(allow_cookies_, browser_options_->allow_cookies); +} + const base::Callback<void(WebPreferences*)>& HeadlessBrowserContextOptions::override_web_preferences_callback() const { return ReturnOverriddenValue( diff --git a/chromium/headless/lib/browser/headless_browser_context_options.h b/chromium/headless/lib/browser/headless_browser_context_options.h index 23a4f6251a7..00e1295f751 100644 --- a/chromium/headless/lib/browser/headless_browser_context_options.h +++ b/chromium/headless/lib/browser/headless_browser_context_options.h @@ -44,6 +44,8 @@ class HeadlessBrowserContextOptions { // Set HeadlessBrowser::Options::incognito_mode. bool incognito_mode() const; + bool allow_cookies() const; + // Custom network protocol handlers. These can be used to override URL // fetching for different network schemes. const ProtocolHandlerMap& protocol_handlers() const; @@ -70,6 +72,7 @@ class HeadlessBrowserContextOptions { base::Optional<gfx::Size> window_size_; base::Optional<base::FilePath> user_data_dir_; base::Optional<bool> incognito_mode_; + base::Optional<bool> allow_cookies_; base::Optional<base::Callback<void(WebPreferences*)>> override_web_preferences_callback_; diff --git a/chromium/headless/lib/browser/headless_browser_impl.cc b/chromium/headless/lib/browser/headless_browser_impl.cc index bbd231c1bb3..88bede99b53 100644 --- a/chromium/headless/lib/browser/headless_browser_impl.cc +++ b/chromium/headless/lib/browser/headless_browser_impl.cc @@ -77,7 +77,7 @@ HeadlessBrowserImpl::HeadlessBrowserImpl( agent_host_(nullptr), weak_ptr_factory_(this) {} -HeadlessBrowserImpl::~HeadlessBrowserImpl() {} +HeadlessBrowserImpl::~HeadlessBrowserImpl() = default; HeadlessBrowserContext::Builder HeadlessBrowserImpl::CreateBrowserContextBuilder() { @@ -103,7 +103,10 @@ void HeadlessBrowserImpl::Shutdown() { weak_ptr_factory_.InvalidateWeakPtrs(); // Destroy all browser contexts. - browser_contexts_.clear(); + { + base::AutoLock lock(browser_contexts_lock_); + browser_contexts_.clear(); + } BrowserMainThread()->PostTask(FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); @@ -112,6 +115,7 @@ void HeadlessBrowserImpl::Shutdown() { std::vector<HeadlessBrowserContext*> HeadlessBrowserImpl::GetAllBrowserContexts() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + base::AutoLock lock(browser_contexts_lock_); std::vector<HeadlessBrowserContext*> result; result.reserve(browser_contexts_.size()); @@ -162,6 +166,7 @@ HeadlessBrowserContext* HeadlessBrowserImpl::CreateBrowserContext( HeadlessBrowserContext* result = browser_context.get(); + base::AutoLock lock(browser_contexts_lock_); browser_contexts_[browser_context->Id()] = std::move(browser_context); return result; @@ -169,6 +174,7 @@ HeadlessBrowserContext* HeadlessBrowserImpl::CreateBrowserContext( void HeadlessBrowserImpl::DestroyBrowserContext( HeadlessBrowserContextImpl* browser_context) { + base::AutoLock lock(browser_contexts_lock_); auto it = browser_contexts_.find(browser_context->Id()); DCHECK(it != browser_contexts_.end()); browser_contexts_.erase(it); @@ -218,12 +224,26 @@ HeadlessWebContentsImpl* HeadlessBrowserImpl::GetWebContentsForWindowId( HeadlessBrowserContext* HeadlessBrowserImpl::GetBrowserContextForId( const std::string& id) { + base::AutoLock lock(browser_contexts_lock_); auto find_it = browser_contexts_.find(id); if (find_it == browser_contexts_.end()) return nullptr; return find_it->second.get(); } +LockedPtr<HeadlessBrowserContextImpl> +HeadlessBrowserImpl::GetBrowserContextForRenderFrame( + int render_process_id, + int render_frame_id) const { + MoveableAutoLock lock(browser_contexts_lock_); + for (const auto& pair : browser_contexts_) { + if (pair.second->GetDevToolsFrameToken(render_process_id, render_frame_id)) + return LockedPtr<HeadlessBrowserContextImpl>(std::move(lock), + pair.second.get()); + } + return LockedPtr<HeadlessBrowserContextImpl>(std::move(lock), nullptr); +} + HeadlessDevToolsTarget* HeadlessBrowserImpl::GetDevToolsTarget() { return agent_host_ ? this : nullptr; } diff --git a/chromium/headless/lib/browser/headless_browser_impl.h b/chromium/headless/lib/browser/headless_browser_impl.h index 591e50a0532..7f4ec64dc20 100644 --- a/chromium/headless/lib/browser/headless_browser_impl.h +++ b/chromium/headless/lib/browser/headless_browser_impl.h @@ -18,6 +18,7 @@ #include "headless/lib/browser/headless_devtools_manager_delegate.h" #include "headless/lib/browser/headless_web_contents_impl.h" #include "headless/public/headless_export.h" +#include "headless/public/util/moveable_auto_lock.h" namespace ui { class Compositor; @@ -78,6 +79,12 @@ class HEADLESS_EXPORT HeadlessBrowserImpl : public HeadlessBrowser, base::WeakPtr<HeadlessBrowserImpl> GetWeakPtr(); + // Returns the corresponding HeadlessBrowserContextImpl or null if one can't + // be found. Can be called on any thread. + LockedPtr<HeadlessBrowserContextImpl> GetBrowserContextForRenderFrame( + int render_process_id, + int render_frame_id) const; + // All the methods that begin with Platform need to be implemented by the // platform specific headless implementation. // Helper for one time initialization of application @@ -93,7 +100,8 @@ class HEADLESS_EXPORT HeadlessBrowserImpl : public HeadlessBrowser, HeadlessBrowser::Options options_; HeadlessBrowserMainParts* browser_main_parts_; // Not owned. - std::unordered_map<std::string, std::unique_ptr<HeadlessBrowserContextImpl>> + mutable base::Lock browser_contexts_lock_; // Protects |browser_contexts_| + base::flat_map<std::string, std::unique_ptr<HeadlessBrowserContextImpl>> browser_contexts_; HeadlessBrowserContext* default_browser_context_; // Not owned. diff --git a/chromium/headless/lib/browser/headless_browser_main_parts.cc b/chromium/headless/lib/browser/headless_browser_main_parts.cc index 64e9018f5e3..da66fb7f75d 100644 --- a/chromium/headless/lib/browser/headless_browser_main_parts.cc +++ b/chromium/headless/lib/browser/headless_browser_main_parts.cc @@ -18,7 +18,7 @@ HeadlessBrowserMainParts::HeadlessBrowserMainParts(HeadlessBrowserImpl* browser) : browser_(browser) , devtools_http_handler_started_(false) {} -HeadlessBrowserMainParts::~HeadlessBrowserMainParts() {} +HeadlessBrowserMainParts::~HeadlessBrowserMainParts() = default; void HeadlessBrowserMainParts::PreMainMessageLoopRun() { const base::CommandLine* command_line = diff --git a/chromium/headless/lib/browser/headless_clipboard.cc b/chromium/headless/lib/browser/headless_clipboard.cc index dcadd4c36c1..9b9fd069746 100644 --- a/chromium/headless/lib/browser/headless_clipboard.cc +++ b/chromium/headless/lib/browser/headless_clipboard.cc @@ -13,7 +13,7 @@ namespace headless { HeadlessClipboard::HeadlessClipboard() : default_store_type_(ui::CLIPBOARD_TYPE_COPY_PASTE) {} -HeadlessClipboard::~HeadlessClipboard() {} +HeadlessClipboard::~HeadlessClipboard() = default; void HeadlessClipboard::OnPreShutdown() {} @@ -182,7 +182,7 @@ HeadlessClipboard::DataStore::DataStore() : sequence_number(0) {} HeadlessClipboard::DataStore::DataStore(const DataStore& other) = default; -HeadlessClipboard::DataStore::~DataStore() {} +HeadlessClipboard::DataStore::~DataStore() = default; void HeadlessClipboard::DataStore::Clear() { data.clear(); diff --git a/chromium/headless/lib/browser/headless_content_browser_client.cc b/chromium/headless/lib/browser/headless_content_browser_client.cc index d96b9886839..98434626717 100644 --- a/chromium/headless/lib/browser/headless_content_browser_client.cc +++ b/chromium/headless/lib/browser/headless_content_browser_client.cc @@ -14,6 +14,7 @@ #include "base/memory/ptr_util.h" #include "base/path_service.h" #include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/resource_dispatcher_host.h" @@ -114,9 +115,11 @@ int GetCrashSignalFD(const base::CommandLine& command_line, HeadlessContentBrowserClient::HeadlessContentBrowserClient( HeadlessBrowserImpl* browser) - : browser_(browser) {} + : browser_(browser), + append_command_line_flags_callback_( + browser_->options()->append_command_line_flags_callback) {} -HeadlessContentBrowserClient::~HeadlessContentBrowserClient() {} +HeadlessContentBrowserClient::~HeadlessContentBrowserClient() = default; content::BrowserMainParts* HeadlessContentBrowserClient::CreateBrowserMainParts( const content::MainFunctionParams&) { @@ -230,6 +233,9 @@ void HeadlessContentBrowserClient::GetAdditionalMappedFilesForChildProcess( void HeadlessContentBrowserClient::AppendExtraCommandLineSwitches( base::CommandLine* command_line, int child_process_id) { + // NOTE: We may be called on the UI or IO thread. If called on the IO thread, + // |browser_| may have already been destroyed. + command_line->AppendSwitch(::switches::kHeadless); const base::CommandLine& old_command_line( *base::CommandLine::ForCurrentProcess()); @@ -245,8 +251,10 @@ void HeadlessContentBrowserClient::AppendExtraCommandLineSwitches( #endif // defined(HEADLESS_USE_BREAKPAD) // If we're spawning a renderer, then override the language switch. - if (command_line->GetSwitchValueASCII(::switches::kProcessType) == - ::switches::kRendererProcess) { + std::string process_type = + command_line->GetSwitchValueASCII(::switches::kProcessType); + if (process_type == ::switches::kRendererProcess) { + // Renderer processes are initialized on the UI thread, so this is safe. content::RenderProcessHost* render_process_host = content::RenderProcessHost::FromID(child_process_id); if (render_process_host) { @@ -262,6 +270,22 @@ void HeadlessContentBrowserClient::AppendExtraCommandLineSwitches( } } } + + if (append_command_line_flags_callback_) { + HeadlessBrowserContextImpl* headless_browser_context_impl = nullptr; + if (process_type == ::switches::kRendererProcess) { + // Renderer processes are initialized on the UI thread, so this is safe. + content::RenderProcessHost* render_process_host = + content::RenderProcessHost::FromID(child_process_id); + if (render_process_host) { + headless_browser_context_impl = HeadlessBrowserContextImpl::From( + render_process_host->GetBrowserContext()); + } + } + append_command_line_flags_callback_.Run(command_line, + headless_browser_context_impl, + process_type, child_process_id); + } } void HeadlessContentBrowserClient::AllowCertificateError( @@ -290,8 +314,7 @@ void HeadlessContentBrowserClient::AllowCertificateError( void HeadlessContentBrowserClient::ResourceDispatcherHostCreated() { resource_dispatcher_host_delegate_.reset( - new HeadlessResourceDispatcherHostDelegate( - browser_->options()->enable_resource_scheduler)); + new HeadlessResourceDispatcherHostDelegate); content::ResourceDispatcherHost::Get()->SetDelegate( resource_dispatcher_host_delegate_.get()); } @@ -300,4 +323,37 @@ net::NetLog* HeadlessContentBrowserClient::GetNetLog() { return browser_->browser_main_parts()->net_log(); } +bool HeadlessContentBrowserClient::AllowGetCookie( + const GURL& url, + const GURL& first_party, + const net::CookieList& cookie_list, + content::ResourceContext* context, + int render_process_id, + int render_frame_id) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + LockedPtr<HeadlessBrowserContextImpl> browser_context = + browser_->GetBrowserContextForRenderFrame(render_process_id, + render_frame_id); + if (!browser_context) + return false; + return browser_context->options()->allow_cookies(); +} + +bool HeadlessContentBrowserClient::AllowSetCookie( + const GURL& url, + const GURL& first_party, + const net::CanonicalCookie& cookie, + content::ResourceContext* context, + int render_process_id, + int render_frame_id, + const net::CookieOptions& options) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + LockedPtr<HeadlessBrowserContextImpl> browser_context = + browser_->GetBrowserContextForRenderFrame(render_process_id, + render_frame_id); + if (!browser_context) + return false; + return browser_context->options()->allow_cookies(); +} + } // namespace headless diff --git a/chromium/headless/lib/browser/headless_content_browser_client.h b/chromium/headless/lib/browser/headless_content_browser_client.h index 929bc9f346b..6a7daae9ad8 100644 --- a/chromium/headless/lib/browser/headless_content_browser_client.h +++ b/chromium/headless/lib/browser/headless_content_browser_client.h @@ -7,6 +7,7 @@ #include "content/public/browser/content_browser_client.h" #include "headless/lib/browser/headless_resource_dispatcher_host_delegate.h" +#include "headless/public/headless_browser.h" namespace headless { @@ -55,6 +56,21 @@ class HeadlessContentBrowserClient : public content::ContentBrowserClient { net::NetLog* GetNetLog() override; + bool AllowGetCookie(const GURL& url, + const GURL& first_party, + const net::CookieList& cookie_list, + content::ResourceContext* context, + int render_process_id, + int render_frame_id) override; + + bool AllowSetCookie(const GURL& url, + const GURL& first_party, + const net::CanonicalCookie& cookie, + content::ResourceContext* context, + int render_process_id, + int render_frame_id, + const net::CookieOptions& options) override; + private: std::unique_ptr<base::Value> GetBrowserServiceManifestOverlay(); std::unique_ptr<base::Value> GetRendererServiceManifestOverlay(); @@ -62,6 +78,10 @@ class HeadlessContentBrowserClient : public content::ContentBrowserClient { HeadlessBrowserImpl* browser_; // Not owned. + // We store the callback here because we may call it from the I/O thread. + HeadlessBrowser::Options::AppendCommandLineFlagsCallback + append_command_line_flags_callback_; + std::unique_ptr<HeadlessResourceDispatcherHostDelegate> resource_dispatcher_host_delegate_; diff --git a/chromium/headless/lib/browser/headless_devtools_client_impl.cc b/chromium/headless/lib/browser/headless_devtools_client_impl.cc index 8cf1e5e5274..9763565c59b 100644 --- a/chromium/headless/lib/browser/headless_devtools_client_impl.cc +++ b/chromium/headless/lib/browser/headless_devtools_client_impl.cc @@ -61,6 +61,7 @@ HeadlessDevToolsClientImpl::HeadlessDevToolsClientImpl() memory_domain_(this), network_domain_(this), page_domain_(this), + performance_domain_(this), profiler_domain_(this), runtime_domain_(this), security_domain_(this), @@ -71,7 +72,7 @@ HeadlessDevToolsClientImpl::HeadlessDevToolsClientImpl() content::BrowserThread::UI)), weak_ptr_factory_(this) {} -HeadlessDevToolsClientImpl::~HeadlessDevToolsClientImpl() {} +HeadlessDevToolsClientImpl::~HeadlessDevToolsClientImpl() = default; bool HeadlessDevToolsClientImpl::AttachToHost( content::DevToolsAgentHost* agent_host) { @@ -115,14 +116,13 @@ void HeadlessDevToolsClientImpl::SendRawDevToolsMessage( #ifndef NDEBUG std::unique_ptr<base::Value> message = base::JSONReader::Read(json_message, base::JSON_PARSE_RFC); - const base::DictionaryValue* message_dict; - int id = 0; - if (!message || !message->GetAsDictionary(&message_dict) || - !message_dict->GetInteger("id", &id)) { - NOTREACHED() << "Badly formed message"; + const base::Value* id_value = message->FindKey("id"); + if (!id_value) { + NOTREACHED() << "Badly formed message " << json_message; return; } - DCHECK_EQ((id % 2), 1) << "Raw devtools messages must have an odd ID."; + DCHECK_EQ((id_value->GetInt() % 2), 1) + << "Raw devtools messages must have an odd ID."; #endif agent_host_->DispatchProtocolMessage(this, json_message); @@ -162,10 +162,10 @@ void HeadlessDevToolsClientImpl::DispatchProtocolMessage( bool HeadlessDevToolsClientImpl::DispatchMessageReply( const base::DictionaryValue& message_dict) { - int id = 0; - if (!message_dict.GetInteger("id", &id)) + const base::Value* id_value = message_dict.FindKey("id"); + if (!id_value) return false; - auto it = pending_messages_.find(id); + auto it = pending_messages_.find(id_value->GetInt()); if (it == pending_messages_.end()) { NOTREACHED() << "Unexpected reply"; return false; @@ -193,9 +193,10 @@ bool HeadlessDevToolsClientImpl::DispatchMessageReply( bool HeadlessDevToolsClientImpl::DispatchEvent( std::unique_ptr<base::Value> owning_message, const base::DictionaryValue& message_dict) { - std::string method; - if (!message_dict.GetString("method", &method)) + const base::Value* method_value = message_dict.FindKey("method"); + if (!method_value) return false; + const std::string& method = method_value->GetString(); if (method == "Inspector.targetCrashed") renderer_crashed_ = true; EventHandlerMap::const_iterator it = event_handlers_.find(method); @@ -228,8 +229,7 @@ void HeadlessDevToolsClientImpl::DispatchEventTask( } void HeadlessDevToolsClientImpl::AgentHostClosed( - content::DevToolsAgentHost* agent_host, - bool replaced_with_another_client) { + content::DevToolsAgentHost* agent_host) { DCHECK_EQ(agent_host_, agent_host); agent_host = nullptr; pending_messages_.clear(); @@ -340,6 +340,10 @@ page::Domain* HeadlessDevToolsClientImpl::GetPage() { return &page_domain_; } +performance::Domain* HeadlessDevToolsClientImpl::GetPerformance() { + return &performance_domain_; +} + profiler::Domain* HeadlessDevToolsClientImpl::GetProfiler() { return &profiler_domain_; } @@ -412,7 +416,7 @@ void HeadlessDevToolsClientImpl::RegisterEventHandler( event_handlers_[method] = callback; } -HeadlessDevToolsClientImpl::Callback::Callback() {} +HeadlessDevToolsClientImpl::Callback::Callback() = default; HeadlessDevToolsClientImpl::Callback::Callback(Callback&& other) = default; @@ -423,7 +427,7 @@ HeadlessDevToolsClientImpl::Callback::Callback( base::Callback<void(const base::Value&)> callback) : callback_with_result(callback) {} -HeadlessDevToolsClientImpl::Callback::~Callback() {} +HeadlessDevToolsClientImpl::Callback::~Callback() = default; HeadlessDevToolsClientImpl::Callback& HeadlessDevToolsClientImpl::Callback:: operator=(Callback&& other) = default; diff --git a/chromium/headless/lib/browser/headless_devtools_manager_delegate.cc b/chromium/headless/lib/browser/headless_devtools_manager_delegate.cc index 498aafcf51f..553d7159b51 100644 --- a/chromium/headless/lib/browser/headless_devtools_manager_delegate.cc +++ b/chromium/headless/lib/browser/headless_devtools_manager_delegate.cc @@ -8,6 +8,7 @@ #include <utility> #include "base/base64.h" +#include "base/command_line.h" #include "base/json/json_writer.h" #include "build/build_config.h" #include "cc/base/switches.h" @@ -20,7 +21,6 @@ #include "headless/grit/headless_lib_resources.h" #include "headless/lib/browser/headless_browser_context_impl.h" #include "headless/lib/browser/headless_browser_impl.h" -#include "headless/lib/browser/headless_network_conditions.h" #include "headless/lib/browser/headless_web_contents_impl.h" #include "headless/public/devtools/domains/target.h" #include "printing/units.h" @@ -155,9 +155,11 @@ void OnBeginFrameFinished( ImageEncoding encoding, int quality, bool has_damage, + bool main_frame_content_updated, std::unique_ptr<SkBitmap> bitmap) { auto result = base::MakeUnique<base::DictionaryValue>(); result->SetBoolean("hasDamage", has_damage); + result->SetBoolean("mainFrameContentUpdated", main_frame_content_updated); if (bitmap && !bitmap->drawsNothing()) { result->SetString("screenshotData", @@ -182,21 +184,42 @@ std::unique_ptr<base::DictionaryValue> ParsePrintSettings( HeadlessPrintSettings* settings) { // We can safely ignore the return values of the following Get methods since // the defaults are already set in |settings|. - params->GetBoolean("landscape", &settings->landscape); - params->GetBoolean("displayHeaderFooter", &settings->display_header_footer); - params->GetBoolean("printBackground", &settings->should_print_backgrounds); - params->GetDouble("scale", &settings->scale); + if (const base::Value* landscape_value = params->FindKey("landscape")) + settings->landscape = landscape_value->GetBool(); + + if (const base::Value* display_header_footer_value = + params->FindKey("displayHeaderFooter")) { + settings->display_header_footer = display_header_footer_value->GetBool(); + } + + if (const base::Value* should_print_backgrounds_value = + params->FindKey("printBackground")) { + settings->should_print_backgrounds = + should_print_backgrounds_value->GetBool(); + } + if (const base::Value* scale_value = params->FindKey("scale")) + settings->scale = scale_value->GetDouble(); if (settings->scale > kScaleMaxVal / 100 || settings->scale < kScaleMinVal / 100) return CreateInvalidParamResponse(command_id, "scale"); - params->GetString("pageRanges", &settings->page_ranges); - params->GetBoolean("ignoreInvalidPageRanges", - &settings->ignore_invalid_page_ranges); + if (const base::Value* page_ranges_value = params->FindKey("pageRanges")) + settings->page_ranges = page_ranges_value->GetString(); + + if (const base::Value* ignore_invalid_page_ranges_value = + params->FindKey("ignoreInvalidPageRanges")) { + settings->ignore_invalid_page_ranges = + ignore_invalid_page_ranges_value->GetBool(); + } double paper_width_in_inch = printing::kLetterWidthInch; + + if (const base::Value* paper_width_value = params->FindKey("paperWidth")) + paper_width_in_inch = paper_width_value->GetDouble(); + double paper_height_in_inch = printing::kLetterHeightInch; - params->GetDouble("paperWidth", &paper_width_in_inch); - params->GetDouble("paperHeight", &paper_height_in_inch); + + if (const base::Value* paper_height_value = params->FindKey("paperHeight")) + paper_height_in_inch = paper_height_value->GetDouble(); if (paper_width_in_inch <= 0) return CreateInvalidParamResponse(command_id, "paperWidth"); if (paper_height_in_inch <= 0) @@ -211,10 +234,19 @@ std::unique_ptr<base::DictionaryValue> ParsePrintSettings( double margin_bottom_in_inch = default_margin_in_inch; double margin_left_in_inch = default_margin_in_inch; double margin_right_in_inch = default_margin_in_inch; - params->GetDouble("marginTop", &margin_top_in_inch); - params->GetDouble("marginBottom", &margin_bottom_in_inch); - params->GetDouble("marginLeft", &margin_left_in_inch); - params->GetDouble("marginRight", &margin_right_in_inch); + + if (const base::Value* margin_top_value = params->FindKey("marginTop")) + margin_top_in_inch = margin_top_value->GetDouble(); + + if (const base::Value* margin_bottom_value = params->FindKey("marginBottom")) + margin_bottom_in_inch = margin_bottom_value->GetDouble(); + + if (const base::Value* margin_left_value = params->FindKey("marginLeft")) + margin_left_in_inch = margin_left_value->GetDouble(); + + if (const base::Value* margin_right_value = params->FindKey("marginRight")) + margin_right_in_inch = margin_right_value->GetDouble(); + if (margin_top_in_inch < 0) return CreateInvalidParamResponse(command_id, "marginTop"); if (margin_bottom_in_inch < 0) @@ -281,7 +313,7 @@ HeadlessDevToolsManagerDelegate::HeadlessDevToolsManagerDelegate( &HeadlessDevToolsManagerDelegate::BeginFrame, base::Unretained(this)); } -HeadlessDevToolsManagerDelegate::~HeadlessDevToolsManagerDelegate() {} +HeadlessDevToolsManagerDelegate::~HeadlessDevToolsManagerDelegate() = default; bool HeadlessDevToolsManagerDelegate::HandleCommand( content::DevToolsAgentHost* agent_host, @@ -292,21 +324,22 @@ bool HeadlessDevToolsManagerDelegate::HandleCommand( if (!browser_) return false; - int id; - std::string method; - if (!command->GetInteger("id", &id) || !command->GetString("method", &method)) + const base::Value* id_value = command->FindKey("id"); + const base::Value* method_value = command->FindKey("method"); + if (!id_value || !method_value) return false; const base::DictionaryValue* params = nullptr; command->GetDictionary("params", ¶ms); + const std::string& method = method_value->GetString(); auto find_it = command_map_.find(method); if (find_it == command_map_.end()) { // Check for any commands that are actioned then passed on to devtools to // handle. find_it = unhandled_command_map_.find(method); if (find_it != unhandled_command_map_.end()) - find_it->second.Run(agent_host, session_id, id, params); + find_it->second.Run(agent_host, session_id, id_value->GetInt(), params); return false; } @@ -315,7 +348,8 @@ bool HeadlessDevToolsManagerDelegate::HandleCommand( agent_host->GetType() != content::DevToolsAgentHost::kTypeBrowser) return false; - auto cmd_result = find_it->second.Run(agent_host, session_id, id, params); + auto cmd_result = + find_it->second.Run(agent_host, session_id, id_value->GetInt(), params); if (!cmd_result) return false; agent_host->SendProtocolMessageToClient(session_id, @@ -333,18 +367,19 @@ bool HeadlessDevToolsManagerDelegate::HandleAsyncCommand( if (!browser_) return false; - int id; - std::string method; - if (!command->GetInteger("id", &id) || !command->GetString("method", &method)) + const base::Value* id_value = command->FindKey("id"); + const base::Value* method_value = command->FindKey("method"); + if (!id_value || !method_value) return false; - auto find_it = async_command_map_.find(method); + auto find_it = async_command_map_.find(method_value->GetString()); if (find_it == async_command_map_.end()) return false; const base::DictionaryValue* params = nullptr; command->GetDictionary("params", ¶ms); - find_it->second.Run(agent_host, session_id, id, params, callback); + find_it->second.Run(agent_host, session_id, id_value->GetInt(), params, + callback); return true; } @@ -432,16 +467,32 @@ HeadlessDevToolsManagerDelegate::CreateTarget( int command_id, const base::DictionaryValue* params) { std::string url; + + if (const base::Value* url_value = params->FindKey("url")) { + url = url_value->GetString(); + } else { + return CreateInvalidParamResponse(command_id, "url"); + } + std::string browser_context_id; + if (const base::Value* browser_context_id_value = + params->FindKey("browserContextId")) { + browser_context_id = browser_context_id_value->GetString(); + } + int width = browser_->options()->window_size.width(); + if (const base::Value* width_value = params->FindKey("width")) + width = width_value->GetInt(); + int height = browser_->options()->window_size.height(); - if (!params || !params->GetString("url", &url)) - return CreateInvalidParamResponse(command_id, "url"); + if (const base::Value* height_value = params->FindKey("height")) + height = height_value->GetInt(); + bool enable_begin_frame_control = false; - params->GetString("browserContextId", &browser_context_id); - params->GetInteger("width", &width); - params->GetInteger("height", &height); - params->GetBoolean("enableBeginFrameControl", &enable_begin_frame_control); + if (const base::Value* enable_begin_frame_control_value = + params->FindKey("enableBeginFrameControl")) { + enable_begin_frame_control = enable_begin_frame_control_value->GetBool(); + } #if defined(OS_MACOSX) if (enable_begin_frame_control) { @@ -488,11 +539,12 @@ HeadlessDevToolsManagerDelegate::CloseTarget( int session_id, int command_id, const base::DictionaryValue* params) { - std::string target_id; - if (!params || !params->GetString("targetId", &target_id)) + const base::Value* target_id_value = params->FindKey("targetId"); + if (!target_id_value) return CreateInvalidParamResponse(command_id, "targetId"); HeadlessWebContents* web_contents = - browser_->GetWebContentsForDevToolsAgentHostId(target_id); + browser_->GetWebContentsForDevToolsAgentHostId( + target_id_value->GetString()); bool success = false; if (web_contents) { web_contents->Close(); @@ -528,11 +580,13 @@ HeadlessDevToolsManagerDelegate::DisposeBrowserContext( int session_id, int command_id, const base::DictionaryValue* params) { - std::string browser_context_id; - if (!params || !params->GetString("browserContextId", &browser_context_id)) + const base::Value* browser_context_id_value = + params->FindKey("browserContextId"); + if (!browser_context_id_value) return CreateInvalidParamResponse(command_id, "browserContextId"); + HeadlessBrowserContext* context = - browser_->GetBrowserContextForId(browser_context_id); + browser_->GetBrowserContextForId(browser_context_id_value->GetString()); bool success = false; if (context && context != browser_->GetDefaultBrowserContext() && @@ -555,12 +609,13 @@ HeadlessDevToolsManagerDelegate::GetWindowForTarget( int session_id, int command_id, const base::DictionaryValue* params) { - std::string target_id; - if (!params->GetString("targetId", &target_id)) + const base::Value* target_id_value = params->FindKey("targetId"); + if (!target_id_value) return CreateInvalidParamResponse(command_id, "targetId"); HeadlessWebContentsImpl* web_contents = HeadlessWebContentsImpl::From( - browser_->GetWebContentsForDevToolsAgentHostId(target_id)); + browser_->GetWebContentsForDevToolsAgentHostId( + target_id_value->GetString())); if (!web_contents) { return CreateErrorResponse(command_id, kErrorServerError, "No web contents for the given target id"); @@ -590,11 +645,11 @@ HeadlessDevToolsManagerDelegate::GetWindowBounds( int session_id, int command_id, const base::DictionaryValue* params) { - int window_id; - if (!params->GetInteger("windowId", &window_id)) + HeadlessWebContentsImpl* web_contents; + const base::Value* window_id_value = params->FindKey("windowId"); + if (!window_id_value || !window_id_value->is_int()) return CreateInvalidParamResponse(command_id, "windowId"); - HeadlessWebContentsImpl* web_contents = - browser_->GetWebContentsForWindowId(window_id); + web_contents = browser_->GetWebContentsForWindowId(window_id_value->GetInt()); if (!web_contents) { return CreateErrorResponse(command_id, kErrorServerError, "Browser window not found"); @@ -611,48 +666,56 @@ HeadlessDevToolsManagerDelegate::SetWindowBounds( int session_id, int command_id, const base::DictionaryValue* params) { - int window_id; - if (!params->GetInteger("windowId", &window_id)) + HeadlessWebContentsImpl* web_contents; + const base::Value* window_id_value = params->FindKey("windowId"); + if (!window_id_value || !window_id_value->is_int()) return CreateInvalidParamResponse(command_id, "windowId"); - HeadlessWebContentsImpl* web_contents = - browser_->GetWebContentsForWindowId(window_id); + web_contents = browser_->GetWebContentsForWindowId(window_id_value->GetInt()); + if (!web_contents) { return CreateErrorResponse(command_id, kErrorServerError, "Browser window not found"); } - const base::Value* value = nullptr; - const base::DictionaryValue* bounds_dict = nullptr; - if (!params->Get("bounds", &value) || !value->GetAsDictionary(&bounds_dict)) + const base::Value* bounds_value = params->FindKey("bounds"); + if (!bounds_value || !bounds_value->is_dict()) return CreateInvalidParamResponse(command_id, "bounds"); std::string window_state; - if (!bounds_dict->GetString("windowState", &window_state)) { + if (const base::Value* window_state_value = + bounds_value->FindKey("windowState")) { + window_state = window_state_value->GetString(); + if (window_state != "normal" && window_state != "minimized" && + window_state != "maximized" && window_state != "fullscreen") { + return CreateInvalidParamResponse(command_id, "windowState"); + } + } else { window_state = "normal"; - } else if (window_state != "normal" && window_state != "minimized" && - window_state != "maximized" && window_state != "fullscreen") { - return CreateInvalidParamResponse(command_id, "windowState"); } // Compute updated bounds when window state is normal. bool set_bounds = false; gfx::Rect bounds = web_contents->web_contents()->GetContainerBounds(); - int left, top, width, height; - if (bounds_dict->GetInteger("left", &left)) { - bounds.set_x(left); + if (const base::Value* left_value = bounds_value->FindKey("left")) { + bounds.set_x(left_value->GetInt()); set_bounds = true; } - if (bounds_dict->GetInteger("top", &top)) { - bounds.set_y(top); + + if (const base::Value* top_value = bounds_value->FindKey("top")) { + bounds.set_y(top_value->GetInt()); set_bounds = true; } - if (bounds_dict->GetInteger("width", &width)) { + + if (const base::Value* width_value = bounds_value->FindKey("width")) { + int width = width_value->GetInt(); if (width < 0) return CreateInvalidParamResponse(command_id, "width"); bounds.set_width(width); set_bounds = true; } - if (bounds_dict->GetInteger("height", &height)) { + + if (const base::Value* height_value = bounds_value->FindKey("height")) { + int height = height_value->GetInt(); if (height < 0) return CreateInvalidParamResponse(command_id, "height"); bounds.set_height(height); @@ -685,19 +748,26 @@ HeadlessDevToolsManagerDelegate::EmulateNetworkConditions( int command_id, const base::DictionaryValue* params) { // Associate NetworkConditions to context - HeadlessBrowserContextImpl* browser_context = - static_cast<HeadlessBrowserContextImpl*>( - browser_->GetDefaultBrowserContext()); - bool offline = false; - double latency = 0, download_throughput = 0, upload_throughput = 0; - params->GetBoolean("offline", &offline); - params->GetDouble("latency", &latency); - params->GetDouble("downloadThroughput", &download_throughput); - params->GetDouble("uploadThroughput", &upload_throughput); + std::vector<HeadlessBrowserContext*> browser_contexts = + browser_->GetAllBrowserContexts(); + if (browser_contexts.empty()) + return CreateSuccessResponse(command_id, nullptr); + const base::Value* offline_value = params->FindKey("offline"); + const base::Value* latency_value = params->FindKey("latency"); + const base::Value* download_throughput_value = + params->FindKey("downloadThroughput"); + const base::Value* upload_throughput_value = + params->FindKey("uploadThroughput"); HeadlessNetworkConditions conditions(HeadlessNetworkConditions( - offline, std::max(latency, 0.0), std::max(download_throughput, 0.0), - std::max(upload_throughput, 0.0))); - browser_context->SetNetworkConditions(conditions); + offline_value ? offline_value->GetBool() : false, + latency_value ? std::max(latency_value->GetDouble(), 0.0) : 0, + download_throughput_value + ? std::max(download_throughput_value->GetDouble(), 0.0) + : 0, + upload_throughput_value + ? std::max(upload_throughput_value->GetDouble(), 0.0) + : 0)); + SetNetworkConditions(browser_contexts, conditions); return CreateSuccessResponse(command_id, nullptr); } @@ -707,13 +777,26 @@ HeadlessDevToolsManagerDelegate::NetworkDisable( int session_id, int command_id, const base::DictionaryValue* params) { - HeadlessBrowserContextImpl* browser_context = - static_cast<HeadlessBrowserContextImpl*>( - browser_->GetDefaultBrowserContext()); - browser_context->SetNetworkConditions(HeadlessNetworkConditions()); + std::vector<HeadlessBrowserContext*> browser_contexts = + browser_->GetAllBrowserContexts(); + if (browser_contexts.empty()) + return CreateSuccessResponse(command_id, nullptr); + SetNetworkConditions(browser_contexts, HeadlessNetworkConditions()); return CreateSuccessResponse(command_id, nullptr); } +void HeadlessDevToolsManagerDelegate::SetNetworkConditions( + std::vector<HeadlessBrowserContext*> browser_contexts, + HeadlessNetworkConditions conditions) { + for (std::vector<HeadlessBrowserContext*>::iterator it = + browser_contexts.begin(); + it != browser_contexts.end(); ++it) { + HeadlessBrowserContextImpl* context = + static_cast<HeadlessBrowserContextImpl*>(*it); + context->SetNetworkConditions(conditions); + } +} + std::unique_ptr<base::DictionaryValue> HeadlessDevToolsManagerDelegate::EnableHeadlessExperimental( content::DevToolsAgentHost* agent_host, @@ -774,24 +857,21 @@ void HeadlessDevToolsManagerDelegate::BeginFrame( return; } - double frame_time_double = 0; - double deadline_double = 0; - double interval_double = 0; - base::Time frame_time; base::TimeTicks frame_timeticks; base::TimeTicks deadline; base::TimeDelta interval; - if (params->GetDouble("frameTime", &frame_time_double)) { - frame_time = base::Time::FromDoubleT(frame_time_double); + if (const base::Value* frame_time_value = params->FindKey("frameTime")) { + frame_time = base::Time::FromJsTime(frame_time_value->GetDouble()); base::TimeDelta delta = frame_time - base::Time::UnixEpoch(); frame_timeticks = base::TimeTicks::UnixEpoch() + delta; } else { frame_timeticks = base::TimeTicks::Now(); } - if (params->GetDouble("interval", &interval_double)) { + if (const base::Value* interval_value = params->FindKey("interval")) { + double interval_double = interval_value->GetDouble(); if (interval_double <= 0) { callback.Run(CreateErrorResponse(command_id, kErrorInvalidParam, "interval has to be greater than 0")); @@ -802,9 +882,9 @@ void HeadlessDevToolsManagerDelegate::BeginFrame( interval = viz::BeginFrameArgs::DefaultInterval(); } - if (params->GetDouble("deadline", &deadline_double)) { + if (const base::Value* deadline_value = params->FindKey("deadline")) { base::TimeDelta delta = - base::Time::FromDoubleT(deadline_double) - frame_time; + base::Time::FromDoubleT(deadline_value->GetDouble()) - frame_time; if (delta <= base::TimeDelta()) { callback.Run(CreateErrorResponse(command_id, kErrorInvalidParam, "deadline has to be after frameTime")); @@ -829,8 +909,8 @@ void HeadlessDevToolsManagerDelegate::BeginFrame( capture_screenshot = true; - std::string format; - if (screenshot_dict->GetString("format", &format)) { + if (const base::Value* format_value = screenshot_dict->FindKey("format")) { + const std::string& format = format_value->GetString(); if (format == kPng) { encoding = ImageEncoding::kPng; } else if (format == kJpeg) { @@ -842,12 +922,14 @@ void HeadlessDevToolsManagerDelegate::BeginFrame( } } - if (screenshot_dict->GetInteger("quality", &quality) && - (quality < 0 || quality > 100)) { - callback.Run( - CreateErrorResponse(command_id, kErrorInvalidParam, - "screenshot.quality has to be in range 0..100")); - return; + if (const base::Value* quality_value = screenshot_dict->FindKey("quality")) { + quality = quality_value->GetInt(); + if (quality < 0 || quality > 100) { + callback.Run(CreateErrorResponse( + command_id, kErrorInvalidParam, + "screenshot.quality has to be in range 0..100")); + return; + } } } diff --git a/chromium/headless/lib/browser/headless_devtools_manager_delegate.h b/chromium/headless/lib/browser/headless_devtools_manager_delegate.h index 9ad18ba1a07..b8fb8a81110 100644 --- a/chromium/headless/lib/browser/headless_devtools_manager_delegate.h +++ b/chromium/headless/lib/browser/headless_devtools_manager_delegate.h @@ -13,6 +13,8 @@ #include "base/memory/weak_ptr.h" #include "base/values.h" #include "content/public/browser/devtools_manager_delegate.h" +#include "headless/lib/browser/headless_network_conditions.h" +#include "headless/public/headless_browser_context.h" #include "printing/features/features.h" #if BUILDFLAG(ENABLE_BASIC_PRINTING) @@ -116,6 +118,10 @@ class HeadlessDevToolsManagerDelegate int command_id, const base::DictionaryValue* params); + void SetNetworkConditions( + std::vector<HeadlessBrowserContext*> browser_contexts, + HeadlessNetworkConditions conditions); + void PrintToPDF(content::DevToolsAgentHost* agent_host, int session_id, int command_id, diff --git a/chromium/headless/lib/browser/headless_focus_client.cc b/chromium/headless/lib/browser/headless_focus_client.cc index 9a4e806e86c..c406c703861 100644 --- a/chromium/headless/lib/browser/headless_focus_client.cc +++ b/chromium/headless/lib/browser/headless_focus_client.cc @@ -12,7 +12,7 @@ namespace headless { HeadlessFocusClient::HeadlessFocusClient() : focused_window_(NULL), observer_manager_(this) {} -HeadlessFocusClient::~HeadlessFocusClient() {} +HeadlessFocusClient::~HeadlessFocusClient() = default; void HeadlessFocusClient::AddObserver( aura::client::FocusChangeObserver* observer) { diff --git a/chromium/headless/lib/browser/headless_network_conditions.cc b/chromium/headless/lib/browser/headless_network_conditions.cc index 3b7c643569e..208208c9020 100644 --- a/chromium/headless/lib/browser/headless_network_conditions.cc +++ b/chromium/headless/lib/browser/headless_network_conditions.cc @@ -21,6 +21,6 @@ HeadlessNetworkConditions::HeadlessNetworkConditions(bool offline, download_throughput(download_throughput), upload_throughput(upload_throughput) {} -HeadlessNetworkConditions::~HeadlessNetworkConditions() {} +HeadlessNetworkConditions::~HeadlessNetworkConditions() = default; } // namespace headless diff --git a/chromium/headless/lib/browser/headless_network_delegate.cc b/chromium/headless/lib/browser/headless_network_delegate.cc index 28859f5f686..51e39600211 100644 --- a/chromium/headless/lib/browser/headless_network_delegate.cc +++ b/chromium/headless/lib/browser/headless_network_delegate.cc @@ -40,7 +40,9 @@ int HeadlessNetworkDelegate::OnBeforeURLRequest( net::URLRequest* request, const net::CompletionCallback& callback, GURL* new_url) { - if (headless_browser_context_->ShouldRemoveHeaders()) { + base::AutoLock lock(lock_); + if (headless_browser_context_ && + headless_browser_context_->ShouldRemoveHeaders()) { request->RemoveRequestHeaderByName( kDevToolsEmulateNetworkConditionsClientId); } @@ -112,7 +114,7 @@ bool HeadlessNetworkDelegate::OnCanGetCookies( } bool HeadlessNetworkDelegate::OnCanSetCookie(const net::URLRequest& request, - const std::string& cookie_line, + const net::CanonicalCookie& cookie, net::CookieOptions* options) { return true; } diff --git a/chromium/headless/lib/browser/headless_network_delegate.h b/chromium/headless/lib/browser/headless_network_delegate.h index ccfb5f63976..66ed81528fc 100644 --- a/chromium/headless/lib/browser/headless_network_delegate.h +++ b/chromium/headless/lib/browser/headless_network_delegate.h @@ -66,7 +66,7 @@ class HeadlessNetworkDelegate : public net::NetworkDelegateImpl, const net::CookieList& cookie_list) override; bool OnCanSetCookie(const net::URLRequest& request, - const std::string& cookie_line, + const net::CanonicalCookie& cookie, net::CookieOptions* options) override; bool OnCanAccessFile(const net::URLRequest& request, diff --git a/chromium/headless/lib/browser/headless_permission_manager.cc b/chromium/headless/lib/browser/headless_permission_manager.cc index a75ee4c3481..8f0fc66e483 100644 --- a/chromium/headless/lib/browser/headless_permission_manager.cc +++ b/chromium/headless/lib/browser/headless_permission_manager.cc @@ -5,13 +5,16 @@ #include "headless/lib/browser/headless_permission_manager.h" #include "base/callback.h" +#include "content/public/browser/browser_context.h" #include "content/public/browser/permission_type.h" namespace headless { -HeadlessPermissionManager::HeadlessPermissionManager() : PermissionManager() {} +HeadlessPermissionManager::HeadlessPermissionManager( + content::BrowserContext* browser_context) + : browser_context_(browser_context) {} -HeadlessPermissionManager::~HeadlessPermissionManager() {} +HeadlessPermissionManager::~HeadlessPermissionManager() = default; int HeadlessPermissionManager::RequestPermission( content::PermissionType permission, @@ -20,7 +23,14 @@ int HeadlessPermissionManager::RequestPermission( bool user_gesture, const base::Callback<void(blink::mojom::PermissionStatus)>& callback) { // In headless mode we just pretent the user "closes" any permission prompt, - // without accepting or denying. + // without accepting or denying. Notifications are the exception to this, + // which are explicitly disabled in Incognito mode. + if (browser_context_->IsOffTheRecord() && + permission == content::PermissionType::NOTIFICATIONS) { + callback.Run(blink::mojom::PermissionStatus::DENIED); + return kNoPendingOperation; + } + callback.Run(blink::mojom::PermissionStatus::ASK); return kNoPendingOperation; } diff --git a/chromium/headless/lib/browser/headless_permission_manager.h b/chromium/headless/lib/browser/headless_permission_manager.h index 5eb31859cc3..f9a12522333 100644 --- a/chromium/headless/lib/browser/headless_permission_manager.h +++ b/chromium/headless/lib/browser/headless_permission_manager.h @@ -9,11 +9,15 @@ #include "base/macros.h" #include "content/public/browser/permission_manager.h" +namespace content { +class BrowserContext; +} + namespace headless { class HeadlessPermissionManager : public content::PermissionManager { public: - HeadlessPermissionManager(); + explicit HeadlessPermissionManager(content::BrowserContext* browser_context); ~HeadlessPermissionManager() override; // PermissionManager implementation. @@ -49,6 +53,8 @@ class HeadlessPermissionManager : public content::PermissionManager { void UnsubscribePermissionStatusChange(int subscription_id) override; private: + content::BrowserContext* browser_context_; + DISALLOW_COPY_AND_ASSIGN(HeadlessPermissionManager); }; diff --git a/chromium/headless/lib/browser/headless_platform_event_source.cc b/chromium/headless/lib/browser/headless_platform_event_source.cc index 7c3100ee4f2..598ec80d1cb 100644 --- a/chromium/headless/lib/browser/headless_platform_event_source.cc +++ b/chromium/headless/lib/browser/headless_platform_event_source.cc @@ -6,8 +6,8 @@ namespace headless { -HeadlessPlatformEventSource::HeadlessPlatformEventSource() {} +HeadlessPlatformEventSource::HeadlessPlatformEventSource() = default; -HeadlessPlatformEventSource::~HeadlessPlatformEventSource() {} +HeadlessPlatformEventSource::~HeadlessPlatformEventSource() = default; } // namespace headless diff --git a/chromium/headless/lib/browser/headless_print_manager.cc b/chromium/headless/lib/browser/headless_print_manager.cc index 2213796df0c..e7a816dcb45 100644 --- a/chromium/headless/lib/browser/headless_print_manager.cc +++ b/chromium/headless/lib/browser/headless_print_manager.cc @@ -51,7 +51,7 @@ HeadlessPrintManager::HeadlessPrintManager(content::WebContents* web_contents) Reset(); } -HeadlessPrintManager::~HeadlessPrintManager() {} +HeadlessPrintManager::~HeadlessPrintManager() = default; // static std::string HeadlessPrintManager::PrintResultToString(PrintResult result) { diff --git a/chromium/headless/lib/browser/headless_quota_permission_context.cc b/chromium/headless/lib/browser/headless_quota_permission_context.cc index 618cbe5c978..b75f01bcf42 100644 --- a/chromium/headless/lib/browser/headless_quota_permission_context.cc +++ b/chromium/headless/lib/browser/headless_quota_permission_context.cc @@ -8,7 +8,7 @@ namespace headless { -HeadlessQuotaPermissionContext::HeadlessQuotaPermissionContext() {} +HeadlessQuotaPermissionContext::HeadlessQuotaPermissionContext() = default; void HeadlessQuotaPermissionContext::RequestQuotaPermission( const content::StorageQuotaParams& params, @@ -24,6 +24,6 @@ void HeadlessQuotaPermissionContext::RequestQuotaPermission( callback.Run(QUOTA_PERMISSION_RESPONSE_ALLOW); } -HeadlessQuotaPermissionContext::~HeadlessQuotaPermissionContext() {} +HeadlessQuotaPermissionContext::~HeadlessQuotaPermissionContext() = default; } // namespace headless diff --git a/chromium/headless/lib/browser/headless_resource_dispatcher_host_delegate.cc b/chromium/headless/lib/browser/headless_resource_dispatcher_host_delegate.cc index b8feeb5a386..d7b4888c527 100644 --- a/chromium/headless/lib/browser/headless_resource_dispatcher_host_delegate.cc +++ b/chromium/headless/lib/browser/headless_resource_dispatcher_host_delegate.cc @@ -6,16 +6,10 @@ namespace headless { -HeadlessResourceDispatcherHostDelegate::HeadlessResourceDispatcherHostDelegate( - bool enable_resource_scheduler) - : enable_resource_scheduler_(enable_resource_scheduler) {} - HeadlessResourceDispatcherHostDelegate:: - ~HeadlessResourceDispatcherHostDelegate() {} + HeadlessResourceDispatcherHostDelegate() = default; -bool HeadlessResourceDispatcherHostDelegate::ShouldUseResourceScheduler() - const { - return enable_resource_scheduler_; -} +HeadlessResourceDispatcherHostDelegate:: + ~HeadlessResourceDispatcherHostDelegate() = default; } // namespace headless diff --git a/chromium/headless/lib/browser/headless_resource_dispatcher_host_delegate.h b/chromium/headless/lib/browser/headless_resource_dispatcher_host_delegate.h index 8e276489478..7600c9bfe05 100644 --- a/chromium/headless/lib/browser/headless_resource_dispatcher_host_delegate.h +++ b/chromium/headless/lib/browser/headless_resource_dispatcher_host_delegate.h @@ -12,15 +12,10 @@ namespace headless { class HeadlessResourceDispatcherHostDelegate : public content::ResourceDispatcherHostDelegate { public: - explicit HeadlessResourceDispatcherHostDelegate( - bool enable_resource_scheduler); + HeadlessResourceDispatcherHostDelegate(); ~HeadlessResourceDispatcherHostDelegate() override; - bool ShouldUseResourceScheduler() const override; - private: - const bool enable_resource_scheduler_; - DISALLOW_COPY_AND_ASSIGN(HeadlessResourceDispatcherHostDelegate); }; diff --git a/chromium/headless/lib/browser/headless_screen.cc b/chromium/headless/lib/browser/headless_screen.cc index 1ee6b64a754..7ce5366b3fa 100644 --- a/chromium/headless/lib/browser/headless_screen.cc +++ b/chromium/headless/lib/browser/headless_screen.cc @@ -21,7 +21,7 @@ HeadlessScreen* HeadlessScreen::Create(const gfx::Size& size) { return new HeadlessScreen(gfx::Rect(size)); } -HeadlessScreen::~HeadlessScreen() {} +HeadlessScreen::~HeadlessScreen() = default; gfx::Point HeadlessScreen::GetCursorScreenPoint() { return aura::Env::GetInstance()->last_mouse_location(); diff --git a/chromium/headless/lib/browser/headless_tab_socket_impl.cc b/chromium/headless/lib/browser/headless_tab_socket_impl.cc index 05e562c4099..cbdb7dc5d79 100644 --- a/chromium/headless/lib/browser/headless_tab_socket_impl.cc +++ b/chromium/headless/lib/browser/headless_tab_socket_impl.cc @@ -19,7 +19,7 @@ HeadlessTabSocketImpl::HeadlessTabSocketImpl(content::WebContents* web_contents) listener_(nullptr), weak_ptr_factory_(this) {} -HeadlessTabSocketImpl::~HeadlessTabSocketImpl() {} +HeadlessTabSocketImpl::~HeadlessTabSocketImpl() = default; // Wrangles the async responses to // HeadlessRenderFrameControllerImpl::InstallTabSocket for which at most one @@ -68,7 +68,7 @@ class TabSocketInstallationController bool success_; friend class base::RefCounted<TabSocketInstallationController>; - ~TabSocketInstallationController() {} + ~TabSocketInstallationController() = default; }; void HeadlessTabSocketImpl::InstallHeadlessTabSocketBindings( 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 d2eaffad364..9df02187acc 100644 --- a/chromium/headless/lib/browser/headless_url_request_context_getter.cc +++ b/chromium/headless/lib/browser/headless_url_request_context_getter.cc @@ -10,18 +10,31 @@ #include "base/memory/ptr_util.h" #include "base/task_scheduler/post_task.h" +#include "build/build_config.h" +#include "components/cookie_config/cookie_store_util.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/cookie_store_factory.h" #include "content/public/browser/devtools_network_transaction_factory.h" +#include "headless/app/headless_shell_switches.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/cookies/cookie_store.h" #include "net/dns/mapped_host_resolver.h" #include "net/http/http_transaction_factory.h" #include "net/http/http_util.h" #include "net/proxy/proxy_service.h" +#include "net/ssl/channel_id_service.h" +#include "net/ssl/default_channel_id_store.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_builder.h" +#if defined(OS_LINUX) && !defined(OS_CHROMEOS) +#include "base/command_line.h" +#include "components/os_crypt/key_storage_config_linux.h" +#include "components/os_crypt/os_crypt.h" +#endif + namespace headless { HeadlessURLRequestContextGetter::HeadlessURLRequestContextGetter( @@ -74,6 +87,51 @@ HeadlessURLRequestContextGetter::GetURLRequestContext() { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); if (!url_request_context_) { net::URLRequestContextBuilder builder; + + { + base::AutoLock lock(lock_); + // Don't store cookies in incognito mode or if no user-data-dir was + // specified + // TODO: Enable this always once saving/restoring sessions is implemented + // (https://crbug.com/617931) + if (headless_browser_context_ && + !headless_browser_context_->IsOffTheRecord() && + !headless_browser_context_->options()->user_data_dir().empty()) { +#if defined(OS_LINUX) && !defined(OS_CHROMEOS) + std::unique_ptr<os_crypt::Config> config(new os_crypt::Config()); + base::CommandLine* command_line = + base::CommandLine::ForCurrentProcess(); + config->store = + command_line->GetSwitchValueASCII(switches::kPasswordStore); + config->product_name = "HeadlessChrome"; + // OSCrypt may target keyring, which requires calls from the main + // thread. + config->main_thread_runner = + content::BrowserThread::GetTaskRunnerForThread( + content::BrowserThread::UI); + config->should_use_preference = false; + config->user_data_path = headless_browser_context_->GetPath(); + OSCrypt::SetConfig(std::move(config)); +#endif + + content::CookieStoreConfig cookie_config( + headless_browser_context_->GetPath().Append( + FILE_PATH_LITERAL("Cookies")), + content::CookieStoreConfig::PERSISTANT_SESSION_COOKIES, NULL); + cookie_config.crypto_delegate = + cookie_config::GetCookieCryptoDelegate(); + std::unique_ptr<net::CookieStore> cookie_store = + CreateCookieStore(cookie_config); + std::unique_ptr<net::ChannelIDService> channel_id_service = + base::MakeUnique<net::ChannelIDService>( + new net::DefaultChannelIDStore(nullptr)); + + cookie_store->SetChannelIDServiceID(channel_id_service->GetUniqueID()); + builder.SetCookieAndChannelIdStores(std::move(cookie_store), + std::move(channel_id_service)); + } + } + builder.set_accept_language( net::HttpUtil::GenerateAcceptLanguageHeader(accept_language_)); builder.set_user_agent(user_agent_); 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 92341b3b497..1fb71c9f48a 100644 --- a/chromium/headless/lib/browser/headless_url_request_context_getter.h +++ b/chromium/headless/lib/browser/headless_url_request_context_getter.h @@ -14,7 +14,6 @@ #include "base/memory/ref_counted.h" #include "base/single_thread_task_runner.h" #include "content/public/browser/browser_context.h" -#include "content/public/browser/content_browser_client.h" #include "headless/public/headless_browser.h" #include "net/proxy/proxy_config.h" #include "net/proxy/proxy_config_service.h" diff --git a/chromium/headless/lib/browser/headless_web_contents_impl.cc b/chromium/headless/lib/browser/headless_web_contents_impl.cc index a3d53b70e4d..cc0c7509625 100644 --- a/chromium/headless/lib/browser/headless_web_contents_impl.cc +++ b/chromium/headless/lib/browser/headless_web_contents_impl.cc @@ -207,13 +207,13 @@ void CreateTabSocketMojoServiceForContents( struct HeadlessWebContentsImpl::PendingFrame { public: - PendingFrame() {} - ~PendingFrame() {} + PendingFrame() = default; + ~PendingFrame() = default; bool MaybeRunCallback() { if (wait_for_copy_result || !display_did_finish_frame) return false; - callback.Run(has_damage, std::move(bitmap)); + callback.Run(has_damage, main_frame_content_updated, std::move(bitmap)); return true; } @@ -221,6 +221,7 @@ struct HeadlessWebContentsImpl::PendingFrame { bool wait_for_copy_result = false; bool display_did_finish_frame = false; bool has_damage = false; + bool main_frame_content_updated = false; std::unique_ptr<SkBitmap> bitmap; FrameFinishedCallback callback; @@ -321,6 +322,7 @@ HeadlessWebContentsImpl::HeadlessWebContentsImpl( weak_ptr_factory_(this) { #if BUILDFLAG(ENABLE_BASIC_PRINTING) && !defined(CHROME_MULTIPLE_DLL_CHILD) HeadlessPrintManager::CreateForWebContents(web_contents); +// TODO(weili): Add support for printing OOPIFs. #endif web_contents->GetMutableRendererPrefs()->accept_languages = browser_context->options()->accept_language(); @@ -595,6 +597,14 @@ void HeadlessWebContentsImpl::DidReceiveCompositorFrame() { for (int session_id : begin_frame_events_enabled_sessions_) agent_host_->SendProtocolMessageToClient(session_id, json_result); } + + // Set main_frame_content_updated on pending frames that the display hasn't + // completed yet. Pending frames that it did complete won't incorporate this + // CompositorFrame. In practice, this should only be a single PendingFrame. + for (const std::unique_ptr<PendingFrame>& pending_frame : pending_frames_) { + if (!pending_frame->display_did_finish_frame) + pending_frame->main_frame_content_updated = true; + } } void HeadlessWebContentsImpl::PendingFrameReadbackComplete( @@ -699,7 +709,7 @@ HeadlessWebContents* HeadlessWebContents::Builder::Build() { return browser_context_->CreateWebContents(this); } -HeadlessWebContents::Builder::MojoService::MojoService() {} +HeadlessWebContents::Builder::MojoService::MojoService() = default; HeadlessWebContents::Builder::MojoService::MojoService( const MojoService& other) = default; @@ -709,6 +719,6 @@ HeadlessWebContents::Builder::MojoService::MojoService( const ServiceFactoryCallback& service_factory) : service_name(service_name), service_factory(service_factory) {} -HeadlessWebContents::Builder::MojoService::~MojoService() {} +HeadlessWebContents::Builder::MojoService::~MojoService() = default; } // namespace headless diff --git a/chromium/headless/lib/browser/headless_web_contents_impl.h b/chromium/headless/lib/browser/headless_web_contents_impl.h index a2095d73f56..a4e06e025ae 100644 --- a/chromium/headless/lib/browser/headless_web_contents_impl.h +++ b/chromium/headless/lib/browser/headless_web_contents_impl.h @@ -143,7 +143,9 @@ class HEADLESS_EXPORT HeadlessWebContentsImpl void SetBeginFrameEventsEnabled(int session_id, bool enabled); using FrameFinishedCallback = - base::Callback<void(bool /*has_damage*/, std::unique_ptr<SkBitmap>)>; + base::Callback<void(bool /* has_damage */, + bool /* main_frame_content_updated */, + std::unique_ptr<SkBitmap>)>; void BeginFrame(const base::TimeTicks& frame_timeticks, const base::TimeTicks& deadline, const base::TimeDelta& interval, @@ -170,7 +172,7 @@ class HEADLESS_EXPORT HeadlessWebContentsImpl const SkBitmap& bitmap, content::ReadbackResponse response); - uint32_t begin_frame_source_id_ = viz::BeginFrameArgs::kManualSourceId; + uint64_t begin_frame_source_id_ = viz::BeginFrameArgs::kManualSourceId; uint64_t begin_frame_sequence_number_ = viz::BeginFrameArgs::kStartingFrameNumber; bool begin_frame_control_enabled_ = false; diff --git a/chromium/headless/lib/dom_tree_extraction_expected_nodes.txt b/chromium/headless/lib/dom_tree_extraction_expected_nodes.txt index 5799ee09c77..588c8b05493 100644 --- a/chromium/headless/lib/dom_tree_extraction_expected_nodes.txt +++ b/chromium/headless/lib/dom_tree_extraction_expected_nodes.txt @@ -237,8 +237,8 @@ "boundingBox": { "height": 200.0, "width": 400.0, - "x": 10.0, - "y": 63.0 + "x": 0.0, + "y": 0.0 }, "childNodeIndexes": [ 20, 21 ], "frameId": "?", @@ -259,8 +259,8 @@ "boundingBox": { "height": 171.0, "width": 384.0, - "x": 18.0, - "y": 71.0 + "x": 8.0, + "y": 8.0 }, "childNodeIndexes": [ 22, 23, 25 ], "layoutNodeIndex": 9, @@ -280,8 +280,8 @@ "boundingBox": { "height": 37.0, "width": 384.0, - "x": 18.0, - "y": 71.0 + "x": 8.0, + "y": 8.0 }, "childNodeIndexes": [ 24 ], "layoutNodeIndex": 10, @@ -520,10 +520,10 @@ { "backendNodeId": 42, "boundingBox": { - "height": 0.0, + "height": 17.0, "width": 0.0, - "x": 0.0, - "y": 0.0 + "x": 8.0, + "y": 329.0 }, "inlineTextNodes": [ { "boundingBox": { diff --git a/chromium/headless/lib/frame_id_browsertest.cc b/chromium/headless/lib/frame_id_browsertest.cc index c2b834938cd..11253124ac4 100644 --- a/chromium/headless/lib/frame_id_browsertest.cc +++ b/chromium/headless/lib/frame_id_browsertest.cc @@ -3,7 +3,6 @@ // found in the LICENSE file. #include "base/bind.h" -#include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/single_thread_task_runner.h" #include "content/public/test/browser_test.h" @@ -11,6 +10,7 @@ #include "headless/lib/browser/headless_web_contents_impl.h" #include "headless/public/devtools/domains/network.h" #include "headless/public/devtools/domains/page.h" +#include "headless/public/devtools/domains/runtime.h" #include "headless/public/headless_devtools_client.h" #include "headless/public/util/testing/test_in_memory_protocol_handler.h" #include "headless/test/headless_browser_test.h" @@ -70,7 +70,8 @@ const char* kStyle3Css = R"( class FrameIdTest : public HeadlessAsyncDevTooledBrowserTest, public network::ExperimentalObserver, - public page::Observer { + public page::Observer, + public runtime::Observer { public: void RunDevTooledTest() override { http_handler_->SetHeadlessBrowserContext(browser_context_); @@ -78,23 +79,38 @@ class FrameIdTest : public HeadlessAsyncDevTooledBrowserTest, EXPECT_TRUE(embedded_test_server()->Start()); devtools_client_->GetNetwork()->GetExperimental()->AddObserver(this); devtools_client_->GetNetwork()->Enable(); + devtools_client_->GetRuntime()->GetExperimental()->AddObserver(this); + devtools_client_->GetRuntime()->Enable(); + + // Enabling the runtime domain will send us the current context. + run_loop_ = base::MakeUnique<base::RunLoop>( + base::RunLoop::Type::kNestableTasksAllowed); + run_loop_->Run(); + run_loop_ = nullptr; + + EXPECT_EQ(1u, execution_context_frame_ids_.size()); + execution_context_frame_ids_.clear(); if (EnableInterception()) { - devtools_client_->GetNetwork() - ->GetExperimental() - ->SetRequestInterceptionEnabled( - network::SetRequestInterceptionEnabledParams::Builder() - .SetEnabled(true) - .Build()); + std::unique_ptr<headless::network::RequestPattern> match_all = + headless::network::RequestPattern::Builder() + .SetUrlPattern("*") + .Build(); + std::vector<std::unique_ptr<headless::network::RequestPattern>> patterns; + patterns.push_back(std::move(match_all)); + devtools_client_->GetNetwork()->GetExperimental()->SetRequestInterception( + network::SetRequestInterceptionParams::Builder() + .SetPatterns(std::move(patterns)) + .Build()); } devtools_client_->GetPage()->AddObserver(this); - base::RunLoop run_loop; - devtools_client_->GetPage()->Enable(run_loop.QuitClosure()); - base::MessageLoop::ScopedNestableTaskAllower nest_loop( - base::MessageLoop::current()); - run_loop.Run(); + run_loop_ = base::MakeUnique<base::RunLoop>( + base::RunLoop::Type::kNestableTasksAllowed); + devtools_client_->GetPage()->Enable(run_loop_->QuitClosure()); + run_loop_->Run(); + run_loop_ = nullptr; devtools_client_->GetPage()->Navigate("http://foo.com/index.html"); } @@ -103,7 +119,7 @@ class FrameIdTest : public HeadlessAsyncDevTooledBrowserTest, ProtocolHandlerMap protocol_handlers; std::unique_ptr<TestInMemoryProtocolHandler> http_handler( new TestInMemoryProtocolHandler(browser()->BrowserIOThread(), - /* simulate_slow_fetch */ false)); + /* request_deferrer */ nullptr)); http_handler_ = http_handler.get(); http_handler_->InsertResponse("http://foo.com/index.html", {kIndexHtml, "text/html"}); @@ -125,20 +141,37 @@ class FrameIdTest : public HeadlessAsyncDevTooledBrowserTest, void OnRequestWillBeSent( const network::RequestWillBeSentParams& params) override { url_to_frame_id_[params.GetRequest()->GetUrl()] = params.GetFrameId(); + frame_ids_.insert(params.GetFrameId()); } // page::Observer implementation: void OnLoadEventFired(const page::LoadEventFiredParams& params) override { EXPECT_THAT(url_to_frame_id_, ContainerEq(http_handler_->url_to_devtools_frame_id())); + EXPECT_THAT(execution_context_frame_ids_, ContainerEq(frame_ids_)); FinishAsynchronousTest(); } virtual bool EnableInterception() const { return false; } + void OnExecutionContextCreated( + const runtime::ExecutionContextCreatedParams& params) override { + const base::Value* frameId = + params.GetContext()->GetAuxData()->FindKey("frameId"); + if (frameId && frameId->is_string()) + execution_context_frame_ids_.insert(frameId->GetString()); + + // If we're nested then exit. + if (run_loop_) + run_loop_->Quit(); + } + private: + std::set<std::string> frame_ids_; + std::set<std::string> execution_context_frame_ids_; std::map<std::string, std::string> url_to_frame_id_; TestInMemoryProtocolHandler* http_handler_; // NOT OWNED + std::unique_ptr<base::RunLoop> run_loop_; }; HEADLESS_ASYNC_DEVTOOLED_TEST_F(FrameIdTest); diff --git a/chromium/headless/lib/headless_browser_browsertest.cc b/chromium/headless/lib/headless_browser_browsertest.cc index 595b75aa7e2..0ed54b6303a 100644 --- a/chromium/headless/lib/headless_browser_browsertest.cc +++ b/chromium/headless/lib/headless_browser_browsertest.cc @@ -279,12 +279,11 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, HttpProtocolHandler) { EXPECT_TRUE(web_contents); EXPECT_TRUE(WaitForLoad(web_contents)); - std::string inner_html; - EXPECT_TRUE(EvaluateScript(web_contents, "document.body.innerHTML") - ->GetResult() - ->GetValue() - ->GetAsString(&inner_html)); - EXPECT_EQ(kResponseBody, inner_html); + EXPECT_EQ(kResponseBody, + EvaluateScript(web_contents, "document.body.innerHTML") + ->GetResult() + ->GetValue() + ->GetString()); } IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, HttpsProtocolHandler) { @@ -309,11 +308,11 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, HttpsProtocolHandler) { EXPECT_TRUE(WaitForLoad(web_contents)); std::string inner_html; - EXPECT_TRUE(EvaluateScript(web_contents, "document.body.innerHTML") - ->GetResult() - ->GetValue() - ->GetAsString(&inner_html)); - EXPECT_EQ(kResponseBody, inner_html); + EXPECT_EQ(kResponseBody, + EvaluateScript(web_contents, "document.body.innerHTML") + ->GetResult() + ->GetValue() + ->GetString()); } IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, WebGLSupported) { @@ -323,15 +322,13 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, WebGLSupported) { HeadlessWebContents* web_contents = browser_context->CreateWebContentsBuilder().Build(); - bool webgl_supported; EXPECT_TRUE( EvaluateScript(web_contents, "(document.createElement('canvas').getContext('webgl')" " instanceof WebGLRenderingContext)") ->GetResult() ->GetValue() - ->GetAsBoolean(&webgl_supported)); - EXPECT_TRUE(webgl_supported); + ->GetBool()); } IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, ClipboardCopyPasteText) { @@ -364,36 +361,30 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, DefaultSizes) { HeadlessBrowser::Options::Builder builder; const HeadlessBrowser::Options kDefaultOptions = builder.Build(); - int screen_width; - int screen_height; - int window_width; - int window_height; - - EXPECT_TRUE(EvaluateScript(web_contents, "screen.width") - ->GetResult() - ->GetValue() - ->GetAsInteger(&screen_width)); - EXPECT_TRUE(EvaluateScript(web_contents, "screen.height") - ->GetResult() - ->GetValue() - ->GetAsInteger(&screen_height)); - EXPECT_TRUE(EvaluateScript(web_contents, "window.innerWidth") - ->GetResult() - ->GetValue() - ->GetAsInteger(&window_width)); - EXPECT_TRUE(EvaluateScript(web_contents, "window.innerHeight") - ->GetResult() - ->GetValue() - ->GetAsInteger(&window_height)); - #if !defined(OS_MACOSX) // On Mac headless does not override the screen dimensions, so they are // left with the actual screen values. - EXPECT_EQ(kDefaultOptions.window_size.width(), screen_width); - EXPECT_EQ(kDefaultOptions.window_size.height(), screen_height); + EXPECT_EQ(kDefaultOptions.window_size.width(), + EvaluateScript(web_contents, "screen.width") + ->GetResult() + ->GetValue() + ->GetInt()); + EXPECT_EQ(kDefaultOptions.window_size.height(), + EvaluateScript(web_contents, "screen.height") + ->GetResult() + ->GetValue() + ->GetInt()); #endif // !defined(OS_MACOSX) - EXPECT_EQ(kDefaultOptions.window_size.width(), window_width); - EXPECT_EQ(kDefaultOptions.window_size.height(), window_height); + EXPECT_EQ(kDefaultOptions.window_size.width(), + EvaluateScript(web_contents, "window.innerWidth") + ->GetResult() + ->GetValue() + ->GetInt()); + EXPECT_EQ(kDefaultOptions.window_size.height(), + EvaluateScript(web_contents, "window.innerHeight") + ->GetResult() + ->GetValue() + ->GetInt()); } namespace { @@ -402,7 +393,7 @@ class ProtocolHandlerWithCookies : public net::URLRequestJobFactory::ProtocolHandler { public: explicit ProtocolHandlerWithCookies(net::CookieList* sent_cookies); - ~ProtocolHandlerWithCookies() override {} + ~ProtocolHandlerWithCookies() override = default; net::URLRequestJob* MaybeCreateJob( net::URLRequest* request, @@ -419,7 +410,7 @@ class URLRequestJobWithCookies : public TestURLRequestJob { URLRequestJobWithCookies(net::URLRequest* request, net::NetworkDelegate* network_delegate, net::CookieList* sent_cookies); - ~URLRequestJobWithCookies() override {} + ~URLRequestJobWithCookies() override = default; // net::URLRequestJob implementation: void Start() override; @@ -457,8 +448,9 @@ void URLRequestJobWithCookies::Start() { options.set_include_httponly(); // See net::URLRequestHttpJob::AddCookieHeaderAndStart(). - url::Origin requested_origin(request_->url()); - url::Origin site_for_cookies(request_->site_for_cookies()); + url::Origin requested_origin = url::Origin::Create(request_->url()); + url::Origin site_for_cookies = + url::Origin::Create(request_->site_for_cookies()); if (net::registry_controlled_domains::SameDomainOrHost( requested_origin, site_for_cookies, @@ -682,7 +674,7 @@ class CrashReporterTest : public HeadlessBrowserTest, inspector::ExperimentalObserver { public: CrashReporterTest() : devtools_client_(HeadlessDevToolsClient::Create()) {} - ~CrashReporterTest() override {} + ~CrashReporterTest() override = default; void SetUp() override { base::ThreadRestrictions::SetIOAllowed(true); @@ -792,7 +784,7 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, PermissionManagerAlwaysASK) { class HeadlessBrowserTestWithNetLog : public HeadlessBrowserTest { public: - HeadlessBrowserTestWithNetLog() {} + HeadlessBrowserTestWithNetLog() = default; void SetUp() override { base::ThreadRestrictions::SetIOAllowed(true); @@ -938,4 +930,45 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, AllowInsecureLocalhostFlag) { EXPECT_TRUE(WaitForLoad(web_contents)); } +class HeadlessBrowserTestAppendCommandLineFlags : public HeadlessBrowserTest { + public: + HeadlessBrowserTestAppendCommandLineFlags() { + options()->append_command_line_flags_callback = base::Bind( + &HeadlessBrowserTestAppendCommandLineFlags::AppendCommandLineFlags, + base::Unretained(this)); + } + + void AppendCommandLineFlags(base::CommandLine* command_line, + HeadlessBrowserContext* child_browser_context, + const std::string& child_process_type, + int child_process_id) { + if (child_process_type != "renderer") + return; + + callback_was_run_ = true; + EXPECT_LE(0, child_process_id); + EXPECT_NE(nullptr, command_line); + EXPECT_NE(nullptr, child_browser_context); + } + + protected: + bool callback_was_run_ = false; +}; + +IN_PROC_BROWSER_TEST_F(HeadlessBrowserTestAppendCommandLineFlags, + AppendChildProcessCommandLineFlags) { + // Create a new renderer process, and verify that callback was executed. + HeadlessBrowserContext* browser_context = + browser()->CreateBrowserContextBuilder().Build(); + HeadlessWebContents* web_contents = + browser_context->CreateWebContentsBuilder() + .SetInitialURL(GURL("about:blank")) + .Build(); + + EXPECT_TRUE(callback_was_run_); + + // Used only for lifetime. + (void)web_contents; +} + } // namespace headless diff --git a/chromium/headless/lib/headless_browser_context_browsertest.cc b/chromium/headless/lib/headless_browser_context_browsertest.cc index 0e48d4cd052..a40b10a78f6 100644 --- a/chromium/headless/lib/headless_browser_context_browsertest.cc +++ b/chromium/headless/lib/headless_browser_context_browsertest.cc @@ -100,9 +100,7 @@ class HeadlessBrowserContextIsolationTest } void OnFirstSetCookieResult(std::unique_ptr<runtime::EvaluateResult> result) { - std::string cookie; - EXPECT_TRUE(result->GetResult()->GetValue()->GetAsString(&cookie)); - EXPECT_EQ(kMainPageCookie, cookie); + EXPECT_EQ(kMainPageCookie, result->GetResult()->GetValue()->GetString()); devtools_client2_->GetRuntime()->Evaluate( base::StringPrintf("document.cookie = '%s'", kIsolatedPageCookie), @@ -113,9 +111,8 @@ class HeadlessBrowserContextIsolationTest void OnSecondSetCookieResult( std::unique_ptr<runtime::EvaluateResult> result) { - std::string cookie; - EXPECT_TRUE(result->GetResult()->GetValue()->GetAsString(&cookie)); - EXPECT_EQ(kIsolatedPageCookie, cookie); + EXPECT_EQ(kIsolatedPageCookie, + result->GetResult()->GetValue()->GetString()); devtools_client_->GetRuntime()->Evaluate( "document.cookie", @@ -124,9 +121,7 @@ class HeadlessBrowserContextIsolationTest } void OnFirstGetCookieResult(std::unique_ptr<runtime::EvaluateResult> result) { - std::string cookie; - EXPECT_TRUE(result->GetResult()->GetValue()->GetAsString(&cookie)); - EXPECT_EQ(kMainPageCookie, cookie); + EXPECT_EQ(kMainPageCookie, result->GetResult()->GetValue()->GetString()); devtools_client2_->GetRuntime()->Evaluate( "document.cookie", @@ -137,9 +132,8 @@ class HeadlessBrowserContextIsolationTest void OnSecondGetCookieResult( std::unique_ptr<runtime::EvaluateResult> result) { - std::string cookie; - EXPECT_TRUE(result->GetResult()->GetValue()->GetAsString(&cookie)); - EXPECT_EQ(kIsolatedPageCookie, cookie); + EXPECT_EQ(kIsolatedPageCookie, + result->GetResult()->GetValue()->GetString()); FinishTest(); } @@ -178,12 +172,11 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, ContextProtocolHandler) { .Build(); EXPECT_TRUE(WaitForLoad(web_contents)); - std::string inner_html; - EXPECT_TRUE(EvaluateScript(web_contents, "document.body.innerHTML") - ->GetResult() - ->GetValue() - ->GetAsString(&inner_html)); - EXPECT_EQ(kResponseBody, inner_html); + EXPECT_EQ(kResponseBody, + EvaluateScript(web_contents, "document.body.innerHTML") + ->GetResult() + ->GetValue() + ->GetString()); web_contents->Close(); HeadlessBrowserContext* another_browser_context = @@ -197,11 +190,10 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, ContextProtocolHandler) { .SetInitialURL(GURL("http://not-an-actual-domain.tld/hello.html")) .Build(); EXPECT_FALSE(WaitForLoad(web_contents)); - EXPECT_TRUE(EvaluateScript(web_contents, "document.body.innerHTML") - ->GetResult() - ->GetValue() - ->GetAsString(&inner_html)); - EXPECT_EQ("", inner_html); + EXPECT_EQ("", EvaluateScript(web_contents, "document.body.innerHTML") + ->GetResult() + ->GetValue() + ->GetString()); web_contents->Close(); } diff --git a/chromium/headless/lib/headless_content_client.cc b/chromium/headless/lib/headless_content_client.cc index fa06d5a176e..8de358fec93 100644 --- a/chromium/headless/lib/headless_content_client.cc +++ b/chromium/headless/lib/headless_content_client.cc @@ -12,7 +12,7 @@ namespace headless { HeadlessContentClient::HeadlessContentClient(HeadlessBrowser::Options* options) : options_(options) {} -HeadlessContentClient::~HeadlessContentClient() {} +HeadlessContentClient::~HeadlessContentClient() = default; std::string HeadlessContentClient::GetProduct() const { return options_->product_name_and_version; diff --git a/chromium/headless/lib/headless_content_main_delegate.cc b/chromium/headless/lib/headless_content_main_delegate.cc index d08c3f646bd..30e19faeec4 100644 --- a/chromium/headless/lib/headless_content_main_delegate.cc +++ b/chromium/headless/lib/headless_content_main_delegate.cc @@ -17,6 +17,7 @@ #include "base/trace_event/trace_event.h" #include "build/build_config.h" #include "components/crash/content/app/breakpad_linux.h" +#include "components/crash/core/common/crash_key.h" #include "content/public/browser/browser_main_runner.h" #include "content/public/common/content_switches.h" #include "headless/lib/browser/headless_browser_impl.h" @@ -85,6 +86,9 @@ bool HeadlessContentMainDelegate::BasicStartupComplete(int* exit_code) { if (browser_->options()->disable_sandbox) command_line->AppendSwitch(switches::kNoSandbox); + if (!browser_->options()->enable_resource_scheduler) + command_line->AppendSwitch(switches::kDisableResourceScheduler); + #if defined(USE_OZONE) // The headless backend is automatically chosen for a headless build, but also // adding it here allows us to run in a non-headless build too. @@ -183,6 +187,8 @@ void HeadlessContentMainDelegate::InitCrashReporter( g_headless_crash_client.Pointer()->set_crash_dumps_dir( browser_->options()->crash_dumps_dir); + crash_reporter::InitializeCrashKeys(); + #if defined(HEADLESS_USE_BREAKPAD) if (!browser_->options()->enable_crash_reporter) { DCHECK(!breakpad::IsCrashReporterEnabled()); @@ -227,7 +233,8 @@ int HeadlessContentMainDelegate::RunProcess( if (!process_type.empty()) return -1; - base::trace_event::TraceLog::GetInstance()->SetProcessName("HeadlessBrowser"); + base::trace_event::TraceLog::GetInstance()->set_process_name( + "HeadlessBrowser"); base::trace_event::TraceLog::GetInstance()->SetProcessSortIndex( kTraceEventBrowserProcessSortIndex); @@ -248,7 +255,7 @@ int HeadlessContentMainDelegate::RunProcess( } #endif // !defined(CHROME_MULTIPLE_DLL_CHILD) -#if !defined(OS_MACOSX) && defined(OS_POSIX) && !defined(OS_ANDROID) +#if defined(OS_LINUX) void HeadlessContentMainDelegate::ZygoteForked() { const base::CommandLine& command_line( *base::CommandLine::ForCurrentProcess()); @@ -261,7 +268,7 @@ void HeadlessContentMainDelegate::ZygoteForked() { breakpad::InitCrashReporter(process_type); #endif } -#endif +#endif // defined(OS_LINUX) // static HeadlessContentMainDelegate* HeadlessContentMainDelegate::GetInstance() { diff --git a/chromium/headless/lib/headless_content_main_delegate.h b/chromium/headless/lib/headless_content_main_delegate.h index 37d935177fc..91343b586ba 100644 --- a/chromium/headless/lib/headless_content_main_delegate.h +++ b/chromium/headless/lib/headless_content_main_delegate.h @@ -46,7 +46,7 @@ class HEADLESS_EXPORT HeadlessContentMainDelegate HeadlessBrowserImpl* browser() const { return browser_.get(); } -#if !defined(OS_MACOSX) && defined(OS_POSIX) && !defined(OS_ANDROID) +#if defined(OS_LINUX) void ZygoteForked() override; #endif diff --git a/chromium/headless/lib/headless_devtools_client_browsertest.cc b/chromium/headless/lib/headless_devtools_client_browsertest.cc index 8f09e03741c..44d8061bea6 100644 --- a/chromium/headless/lib/headless_devtools_client_browsertest.cc +++ b/chromium/headless/lib/headless_devtools_client_browsertest.cc @@ -8,7 +8,6 @@ #include "base/files/file_util.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" -#include "base/message_loop/message_loop.h" #include "base/path_service.h" #include "base/run_loop.h" #include "base/strings/string_util.h" @@ -44,6 +43,7 @@ } while (false) using testing::ElementsAre; +using testing::NotNull; namespace headless { @@ -75,9 +75,7 @@ class HeadlessDevToolsClientNavigationTest .SetUrl(embedded_test_server()->GetURL("/hello.html").spec()) .Build(); devtools_client_->GetPage()->GetExperimental()->AddObserver(this); - base::RunLoop run_loop; - base::MessageLoop::ScopedNestableTaskAllower nest_loop( - base::MessageLoop::current()); + base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); devtools_client_->GetPage()->Enable(run_loop.QuitClosure()); run_loop.Run(); devtools_client_->GetPage()->Navigate(std::move(params)); @@ -275,17 +273,13 @@ class HeadlessDevToolsClientEvalTest } void OnFirstResult(std::unique_ptr<runtime::EvaluateResult> result) { - int value; EXPECT_TRUE(result->GetResult()->HasValue()); - EXPECT_TRUE(result->GetResult()->GetValue()->GetAsInteger(&value)); - EXPECT_EQ(3, value); + EXPECT_EQ(3, result->GetResult()->GetValue()->GetInt()); } void OnSecondResult(std::unique_ptr<runtime::EvaluateResult> result) { - int value; EXPECT_TRUE(result->GetResult()->HasValue()); - EXPECT_TRUE(result->GetResult()->GetValue()->GetAsInteger(&value)); - EXPECT_EQ(168, value); + EXPECT_EQ(168, result->GetResult()->GetValue()->GetInt()); FinishAsynchronousTest(); } }; @@ -334,11 +328,9 @@ class HeadlessDevToolsClientObserverTest public: void RunDevTooledTest() override { EXPECT_TRUE(embedded_test_server()->Start()); - base::RunLoop run_loop; + base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); devtools_client_->GetNetwork()->AddObserver(this); devtools_client_->GetNetwork()->Enable(run_loop.QuitClosure()); - base::MessageLoop::ScopedNestableTaskAllower nest_loop( - base::MessageLoop::current()); run_loop.Run(); devtools_client_->GetPage()->Navigate( @@ -356,10 +348,10 @@ class HeadlessDevToolsClientObserverTest const network::ResponseReceivedParams& params) override { EXPECT_EQ(200, params.GetResponse()->GetStatus()); EXPECT_EQ("OK", params.GetResponse()->GetStatusText()); - std::string content_type; - EXPECT_TRUE(params.GetResponse()->GetHeaders()->GetString("Content-Type", - &content_type)); - EXPECT_EQ("text/html", content_type); + const base::Value* content_type_value = + params.GetResponse()->GetHeaders()->FindKey("Content-Type"); + ASSERT_THAT(content_type_value, NotNull()); + EXPECT_EQ("text/html", content_type_value->GetString()); devtools_client_->GetNetwork()->Disable(); devtools_client_->GetNetwork()->RemoveObserver(this); @@ -375,11 +367,9 @@ class HeadlessDevToolsClientExperimentalTest public: void RunDevTooledTest() override { EXPECT_TRUE(embedded_test_server()->Start()); - base::RunLoop run_loop; + base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); devtools_client_->GetPage()->GetExperimental()->AddObserver(this); devtools_client_->GetPage()->Enable(run_loop.QuitClosure()); - base::MessageLoop::ScopedNestableTaskAllower nest_loop( - base::MessageLoop::current()); run_loop.Run(); // Check that experimental commands require parameter objects. devtools_client_->GetRuntime() @@ -612,11 +602,9 @@ class TargetDomainCreateTwoContexts : public HeadlessAsyncDevTooledBrowserTest, void RunDevTooledTest() override { EXPECT_TRUE(embedded_test_server()->Start()); - base::RunLoop run_loop; + base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); devtools_client_->GetPage()->AddObserver(this); devtools_client_->GetPage()->Enable(run_loop.QuitClosure()); - base::MessageLoop::ScopedNestableTaskAllower nest_loop( - base::MessageLoop::current()); run_loop.Run(); devtools_client_->GetTarget()->GetExperimental()->AddObserver(this); @@ -758,9 +746,8 @@ class TargetDomainCreateTwoContexts : public HeadlessAsyncDevTooledBrowserTest, return; } - std::string method; - if (message_dict->GetString("method", &method) && - method == "Page.loadEventFired") { + const base::Value* method_value = message_dict->FindKey("method"); + if (method_value && method_value->GetString() == "Page.loadEventFired") { if (params.GetTargetId() == page_id_one_) { page_one_loaded_ = true; } else if (params.GetTargetId() == page_id_two_) { @@ -770,9 +757,10 @@ class TargetDomainCreateTwoContexts : public HeadlessAsyncDevTooledBrowserTest, return; } - int message_id = 0; - if (!message_dict->GetInteger("id", &message_id)) + const base::Value* id_value = message_dict->FindKey("id"); + if (!id_value) return; + int message_id = id_value->GetInt(); const base::DictionaryValue* result_dict; if (message_dict->GetDictionary("result", &result_dict)) { if (message_id == 101) { @@ -811,9 +799,10 @@ class TargetDomainCreateTwoContexts : public HeadlessAsyncDevTooledBrowserTest, // There's a nested result. We want the inner one. EXPECT_TRUE(result_dict->GetDictionary("result", &result_dict)); - std::string value; - EXPECT_TRUE(result_dict->GetString("value", &value)); - EXPECT_EQ("", value) << "Page 2 should not share cookies from page one"; + const base::Value* value_value = result_dict->FindKey("value"); + ASSERT_THAT(value_value, NotNull()); + EXPECT_EQ("", value_value->GetString()) + << "Page 2 should not share cookies from page one"; devtools_client_->GetTarget()->GetExperimental()->CloseTarget( target::CloseTargetParams::Builder() @@ -884,20 +873,21 @@ class HeadlessDevToolsNavigationControlTest public: void RunDevTooledTest() override { EXPECT_TRUE(embedded_test_server()->Start()); - base::RunLoop run_loop; + base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); 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_->GetNetwork()->Enable(); - devtools_client_->GetNetwork() - ->GetExperimental() - ->SetRequestInterceptionEnabled( - network::SetRequestInterceptionEnabledParams::Builder() - .SetEnabled(true) - .Build()); + + std::unique_ptr<headless::network::RequestPattern> match_all = + headless::network::RequestPattern::Builder().SetUrlPattern("*").Build(); + std::vector<std::unique_ptr<headless::network::RequestPattern>> patterns; + patterns.push_back(std::move(match_all)); + devtools_client_->GetNetwork()->GetExperimental()->SetRequestInterception( + network::SetRequestInterceptionParams::Builder() + .SetPatterns(std::move(patterns)) + .Build()); devtools_client_->GetPage()->Navigate( embedded_test_server()->GetURL("/hello.html").spec()); } @@ -980,10 +970,8 @@ class HeadlessDevToolsClientAttachTest } void OnFirstResult(std::unique_ptr<runtime::EvaluateResult> result) { - int value; EXPECT_TRUE(result->GetResult()->HasValue()); - EXPECT_TRUE(result->GetResult()->GetValue()->GetAsInteger(&value)); - EXPECT_EQ(24 * 7, value); + EXPECT_EQ(24 * 7, result->GetResult()->GetValue()->GetInt()); HeadlessDevToolsTarget* devtools_target = web_contents_->GetDevToolsTarget(); @@ -999,10 +987,8 @@ class HeadlessDevToolsClientAttachTest } void OnSecondResult(std::unique_ptr<runtime::EvaluateResult> result) { - int value; EXPECT_TRUE(result->GetResult()->HasValue()); - EXPECT_TRUE(result->GetResult()->GetValue()->GetAsInteger(&value)); - EXPECT_EQ(27 * 4, value); + EXPECT_EQ(27 * 4, result->GetResult()->GetValue()->GetInt()); // If everything worked, this call will not crash, since it // detaches devtools_client_. @@ -1021,11 +1007,9 @@ class HeadlessDevToolsMethodCallErrorTest public: void RunDevTooledTest() override { EXPECT_TRUE(embedded_test_server()->Start()); - base::RunLoop run_loop; + base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); devtools_client_->GetPage()->AddObserver(this); devtools_client_->GetPage()->Enable(run_loop.QuitClosure()); - base::MessageLoop::ScopedNestableTaskAllower nest_loop( - base::MessageLoop::current()); run_loop.Run(); devtools_client_->GetPage()->Navigate( embedded_test_server()->GetURL("/hello.html").spec()); @@ -1063,13 +1047,11 @@ class HeadlessDevToolsNetworkBlockedUrlTest public: void RunDevTooledTest() override { EXPECT_TRUE(embedded_test_server()->Start()); - base::RunLoop run_loop; + base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); devtools_client_->GetPage()->AddObserver(this); devtools_client_->GetPage()->Enable(); devtools_client_->GetNetwork()->AddObserver(this); devtools_client_->GetNetwork()->Enable(run_loop.QuitClosure()); - base::MessageLoop::ScopedNestableTaskAllower nest_loop( - base::MessageLoop::current()); run_loop.Run(); std::vector<std::string> blockedUrls; blockedUrls.push_back("dom_tree_test.css"); @@ -1131,14 +1113,12 @@ class DevToolsHeaderStrippingTest : public HeadlessAsyncDevTooledBrowserTest, public network::Observer { void RunDevTooledTest() override { EXPECT_TRUE(embedded_test_server()->Start()); - base::RunLoop run_loop; + base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); devtools_client_->GetPage()->AddObserver(this); devtools_client_->GetPage()->Enable(); // Enable network domain in order to get DevTools to add the header. devtools_client_->GetNetwork()->AddObserver(this); devtools_client_->GetNetwork()->Enable(run_loop.QuitClosure()); - base::MessageLoop::ScopedNestableTaskAllower nest_loop( - base::MessageLoop::current()); run_loop.Run(); devtools_client_->GetPage()->Navigate( "http://not-an-actual-domain.tld/hello.html"); @@ -1166,6 +1146,38 @@ class DevToolsHeaderStrippingTest : public HeadlessAsyncDevTooledBrowserTest, HEADLESS_ASYNC_DEVTOOLED_TEST_F(DevToolsHeaderStrippingTest); +class DevToolsNetworkOfflineEmulationTest + : public HeadlessAsyncDevTooledBrowserTest, + public page::Observer, + public network::Observer { + void RunDevTooledTest() override { + EXPECT_TRUE(embedded_test_server()->Start()); + base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); + devtools_client_->GetPage()->AddObserver(this); + devtools_client_->GetPage()->Enable(); + devtools_client_->GetNetwork()->AddObserver(this); + devtools_client_->GetNetwork()->Enable(run_loop.QuitClosure()); + run_loop.Run(); + std::unique_ptr<network::EmulateNetworkConditionsParams> params = + network::EmulateNetworkConditionsParams::Builder() + .SetOffline(true) + .SetLatency(0) + .SetDownloadThroughput(0) + .SetUploadThroughput(0) + .Build(); + devtools_client_->GetNetwork()->EmulateNetworkConditions(std::move(params)); + devtools_client_->GetPage()->Navigate( + embedded_test_server()->GetURL("/hello.html").spec()); + } + + void OnLoadingFailed(const network::LoadingFailedParams& failed) override { + EXPECT_EQ("net::ERR_INTERNET_DISCONNECTED", failed.GetErrorText()); + FinishAsynchronousTest(); + } +}; + +HEADLESS_ASYNC_DEVTOOLED_TEST_F(DevToolsNetworkOfflineEmulationTest); + class RawDevtoolsProtocolTest : public HeadlessAsyncDevTooledBrowserTest, public HeadlessDevToolsClient::RawProtocolListener { @@ -1263,24 +1275,27 @@ class DomTreeExtractionBrowserTest : public HeadlessAsyncDevTooledBrowserTest, base::DictionaryValue* node_dict = dom_nodes[i].get(); // Frame IDs are random. - if (node_dict->HasKey("frameId")) + if (node_dict->FindKey("frameId")) node_dict->SetString("frameId", "?"); // Ports are random. - std::string url; - if (node_dict->GetString("baseURL", &url)) { - node_dict->SetString("baseURL", - GURL(url).ReplaceComponents(replace_port).spec()); + if (base::Value* base_url_value = node_dict->FindKey("baseURL")) { + node_dict->SetString("baseURL", GURL(base_url_value->GetString()) + .ReplaceComponents(replace_port) + .spec()); } - if (node_dict->GetString("documentURL", &url)) { + if (base::Value* document_url_value = node_dict->FindKey("documentURL")) { node_dict->SetString("documentURL", - GURL(url).ReplaceComponents(replace_port).spec()); + GURL(document_url_value->GetString()) + .ReplaceComponents(replace_port) + .spec()); } // Merge LayoutTreeNode data into the dictionary. - int layout_node_index; - if (node_dict->GetInteger("layoutNodeIndex", &layout_node_index)) { + if (base::Value* layout_node_index_value = + node_dict->FindKey("layoutNodeIndex")) { + int layout_node_index = layout_node_index_value->GetInt(); ASSERT_LE(0, layout_node_index); ASSERT_GT(result->GetLayoutTreeNodes()->size(), static_cast<size_t>(layout_node_index)); @@ -1299,8 +1314,8 @@ class DomTreeExtractionBrowserTest : public HeadlessAsyncDevTooledBrowserTest, if (layout_node->HasInlineTextNodes()) { std::unique_ptr<base::ListValue> inline_text_nodes( new base::ListValue()); - for (const std::unique_ptr<css::InlineTextBox>& inline_text_box : - *layout_node->GetInlineTextNodes()) { + for (const std::unique_ptr<dom_snapshot::InlineTextBox>& + inline_text_box : *layout_node->GetInlineTextNodes()) { size_t index = inline_text_nodes->GetSize(); inline_text_nodes->Set(index, inline_text_box->Serialize()); } @@ -1384,21 +1399,22 @@ class UrlRequestFailedTest : public HeadlessAsyncDevTooledBrowserTest, EXPECT_TRUE(embedded_test_server()->Start()); devtools_client_->GetNetwork()->GetExperimental()->AddObserver(this); devtools_client_->GetNetwork()->Enable(); - devtools_client_->GetNetwork() - ->GetExperimental() - ->SetRequestInterceptionEnabled( - network::SetRequestInterceptionEnabledParams::Builder() - .SetEnabled(true) - .Build()); + + std::unique_ptr<headless::network::RequestPattern> match_all = + headless::network::RequestPattern::Builder().SetUrlPattern("*").Build(); + std::vector<std::unique_ptr<headless::network::RequestPattern>> patterns; + patterns.push_back(std::move(match_all)); + devtools_client_->GetNetwork()->GetExperimental()->SetRequestInterception( + network::SetRequestInterceptionParams::Builder() + .SetPatterns(std::move(patterns)) + .Build()); browser_context_->AddObserver(this); devtools_client_->GetPage()->AddObserver(this); - base::RunLoop run_loop; + base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); devtools_client_->GetPage()->Enable(run_loop.QuitClosure()); - base::MessageLoop::ScopedNestableTaskAllower nest_loop( - base::MessageLoop::current()); run_loop.Run(); devtools_client_->GetPage()->Navigate( @@ -1484,10 +1500,8 @@ class DevToolsSetCookieTest : public HeadlessAsyncDevTooledBrowserTest, EXPECT_TRUE(embedded_test_server()->Start()); devtools_client_->GetNetwork()->AddObserver(this); - base::RunLoop run_loop; + base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); devtools_client_->GetNetwork()->Enable(run_loop.QuitClosure()); - base::MessageLoop::ScopedNestableTaskAllower nest_loop( - base::MessageLoop::current()); run_loop.Run(); devtools_client_->GetPage()->Navigate( @@ -1523,19 +1537,19 @@ class DevtoolsInterceptionWithAuthProxyTest EXPECT_TRUE(embedded_test_server()->Start()); devtools_client_->GetNetwork()->GetExperimental()->AddObserver(this); devtools_client_->GetNetwork()->Enable(); - devtools_client_->GetNetwork() - ->GetExperimental() - ->SetRequestInterceptionEnabled( - network::SetRequestInterceptionEnabledParams::Builder() - .SetEnabled(true) - .Build()); + std::unique_ptr<headless::network::RequestPattern> match_all = + headless::network::RequestPattern::Builder().SetUrlPattern("*").Build(); + std::vector<std::unique_ptr<headless::network::RequestPattern>> patterns; + patterns.push_back(std::move(match_all)); + devtools_client_->GetNetwork()->GetExperimental()->SetRequestInterception( + network::SetRequestInterceptionParams::Builder() + .SetPatterns(std::move(patterns)) + .Build()); devtools_client_->GetPage()->AddObserver(this); - base::RunLoop run_loop; + base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); devtools_client_->GetPage()->Enable(run_loop.QuitClosure()); - base::MessageLoop::ScopedNestableTaskAllower nest_loop( - base::MessageLoop::current()); run_loop.Run(); devtools_client_->GetPage()->Navigate( @@ -1604,10 +1618,9 @@ class NavigatorLanguages : public HeadlessAsyncDevTooledBrowserTest { } 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); + EXPECT_EQ("[\"en-UK\",\"DE\",\"FR\"]", + result->GetResult()->GetValue()->GetString()); FinishAsynchronousTest(); } diff --git a/chromium/headless/lib/headless_web_contents_browsertest.cc b/chromium/headless/lib/headless_web_contents_browsertest.cc index 34270082532..17b264ec842 100644 --- a/chromium/headless/lib/headless_web_contents_browsertest.cc +++ b/chromium/headless/lib/headless_web_contents_browsertest.cc @@ -9,12 +9,12 @@ #include "base/base64.h" #include "base/command_line.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 "cc/base/switches.h" #include "content/public/browser/web_contents.h" +#include "content/public/common/content_switches.h" #include "content/public/test/browser_test.h" #include "headless/lib/browser/headless_web_contents_impl.h" #include "headless/public/devtools/domains/dom_snapshot.h" @@ -66,8 +66,8 @@ class MockHeadlessBrowserContextObserver MOCK_METHOD2(OnChildContentsCreated, void(HeadlessWebContents*, HeadlessWebContents*)); - MockHeadlessBrowserContextObserver() {} - virtual ~MockHeadlessBrowserContextObserver() {} + MockHeadlessBrowserContextObserver() = default; + virtual ~MockHeadlessBrowserContextObserver() = default; HeadlessWebContents* last_parent; HeadlessWebContents* last_child; @@ -186,21 +186,23 @@ class HeadlessWindowOpenTabSocketTest : public HeadlessBrowserTest, // runtime::Observer implementation. void OnExecutionContextCreated( const runtime::ExecutionContextCreatedParams& params) override { - const base::DictionaryValue* dictionary; std::string frame_id; - if (params.GetContext()->HasAuxData() && - params.GetContext()->GetAuxData()->GetAsDictionary(&dictionary) && - dictionary->GetString("frameId", &frame_id) && - frame_id == *child_frame_id_) { - child_frame_execution_context_id_ = params.GetContext()->GetId(); - - HeadlessTabSocket* tab_socket = child_->GetHeadlessTabSocket(); - CHECK(tab_socket); - tab_socket->InstallHeadlessTabSocketBindings( - *child_frame_execution_context_id_, - base::Bind(&HeadlessWindowOpenTabSocketTest::OnTabSocketInstalled, - base::Unretained(this))); - } + if (!params.GetContext()->HasAuxData()) + return; + + const base::Value* frame_id_value = + params.GetContext()->GetAuxData()->FindKey("frameId"); + if (!frame_id_value || frame_id_value->GetString() != *child_frame_id_) + return; + + child_frame_execution_context_id_ = params.GetContext()->GetId(); + + HeadlessTabSocket* tab_socket = child_->GetHeadlessTabSocket(); + CHECK(tab_socket); + tab_socket->InstallHeadlessTabSocketBindings( + *child_frame_execution_context_id_, + base::Bind(&HeadlessWindowOpenTabSocketTest::OnTabSocketInstalled, + base::Unretained(this))); } void OnTabSocketInstalled(bool success) { @@ -259,7 +261,7 @@ IN_PROC_BROWSER_TEST_F(HeadlessWindowOpenTabSocketTest, class HeadlessNoDevToolsTabSocketTest : public HeadlessBrowserTest, public HeadlessTabSocket::Listener { public: - HeadlessNoDevToolsTabSocketTest() {} + HeadlessNoDevToolsTabSocketTest() = default; void SetUp() override { options()->mojo_service_names.insert("headless::TabSocket"); @@ -328,12 +330,9 @@ IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, Focus) { .Build(); EXPECT_TRUE(WaitForLoad(web_contents)); - bool result; - EXPECT_TRUE(EvaluateScript(web_contents, "document.hasFocus()") - ->GetResult() - ->GetValue() - ->GetAsBoolean(&result)); - EXPECT_TRUE(result); + std::unique_ptr<runtime::EvaluateResult> has_focus = + EvaluateScript(web_contents, "document.hasFocus()"); + EXPECT_TRUE(has_focus->GetResult()->GetValue()->GetBool()); HeadlessWebContents* web_contents2 = browser_context->CreateWebContentsBuilder() @@ -342,16 +341,11 @@ IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, Focus) { EXPECT_TRUE(WaitForLoad(web_contents2)); // Focus of different WebContents is independent. - EXPECT_TRUE(EvaluateScript(web_contents, "document.hasFocus()") - ->GetResult() - ->GetValue() - ->GetAsBoolean(&result)); - EXPECT_TRUE(result); - EXPECT_TRUE(EvaluateScript(web_contents2, "document.hasFocus()") - ->GetResult() - ->GetValue() - ->GetAsBoolean(&result)); - EXPECT_TRUE(result); + has_focus = EvaluateScript(web_contents, "document.hasFocus()"); + EXPECT_TRUE(has_focus->GetResult()->GetValue()->GetBool()); + + has_focus = EvaluateScript(web_contents2, "document.hasFocus()"); + EXPECT_TRUE(has_focus->GetResult()->GetValue()->GetBool()); } IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, HandleSSLError) { @@ -449,7 +443,7 @@ class HeadlessWebContentsPDFTest : public HeadlessAsyncDevTooledBrowserTest { void RunDevTooledTest() override { std::string height_expression = "document.body.style.height = '" + - base::DoubleToString(kDocHeight) + "in'"; + base::NumberToString(kDocHeight) + "in'"; std::unique_ptr<runtime::EvaluateParams> params = runtime::EvaluateParams::Builder() .SetExpression("document.body.style.background = '#123456';" + @@ -855,9 +849,7 @@ class HeadlessWebContentsRequestStorageQuotaTest void RunDevTooledTest() override { EXPECT_TRUE(embedded_test_server()->Start()); - base::RunLoop run_loop; - base::MessageLoop::ScopedNestableTaskAllower nest_loop( - base::MessageLoop::current()); + base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); devtools_client_->GetRuntime()->AddObserver(this); devtools_client_->GetRuntime()->Enable(run_loop.QuitClosure()); run_loop.Run(); @@ -927,37 +919,35 @@ namespace { const char* kRequestOrderTestPage = R"( <html> <body> - <img src="img0"> - <img src="img1"> - <img src="img2"> - <img src="img3"> - <img src="img4"> - <img src="img5"> - <img src="img6"> + <script src='script1' async></script> + <script src='script2' async></script> + <script src='script3' async></script> + <script src='script4' async></script> + <script src='script5' async></script> + <script src='script6' async></script> <script src='script7' async></script> - <script> - var xhr = new XMLHttpRequest(); - xhr.open('GET', 'xhr8'); - xhr.send(); - </script> - <iframe src=frame9></iframe> - <img src="img10"> - <img src="img11"> + <script src='script8' async></script> + <script src='script9' async></script> + <script src='script10' async></script> + <script src='script11' async></script> + <script src='script12' async></script> + <script src='script13' async></script> + <script src='script14' async></script> + <script src='script15' async></script> + <script src='script16' async></script> + <script src='script17' async></script> + <script src='script18' async></script> + <script src='script19' async></script> + <script src='script20' async></script> </body> </html> )"; -const char* kRequestOrderTestPageUrls[] = { - "http://foo.com/index.html", "http://foo.com/img0", - "http://foo.com/img1", "http://foo.com/img2", - "http://foo.com/img3", "http://foo.com/img4", - "http://foo.com/img5", "http://foo.com/img6", - "http://foo.com/script7", "http://foo.com/img10", - "http://foo.com/img11", "http://foo.com/xhr8", - "http://foo.com/frame9"}; } // namespace -class ResourceSchedulerTest : public HeadlessAsyncDevTooledBrowserTest, - public page::Observer { +class ResourceSchedulerTest + : public HeadlessAsyncDevTooledBrowserTest, + public page::Observer, + public TestInMemoryProtocolHandler::RequestDeferrer { public: void SetUp() override { options()->enable_resource_scheduler = GetEnableResourceScheduler(); @@ -968,10 +958,8 @@ class ResourceSchedulerTest : public HeadlessAsyncDevTooledBrowserTest, http_handler_->SetHeadlessBrowserContext(browser_context_); devtools_client_->GetPage()->AddObserver(this); - base::RunLoop run_loop; + base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); devtools_client_->GetPage()->Enable(run_loop.QuitClosure()); - base::MessageLoop::ScopedNestableTaskAllower nest_loop( - base::MessageLoop::current()); run_loop.Run(); devtools_client_->GetPage()->Navigate("http://foo.com/index.html"); @@ -982,8 +970,7 @@ class ResourceSchedulerTest : public HeadlessAsyncDevTooledBrowserTest, ProtocolHandlerMap GetProtocolHandlers() override { ProtocolHandlerMap protocol_handlers; std::unique_ptr<TestInMemoryProtocolHandler> http_handler( - new TestInMemoryProtocolHandler(browser()->BrowserIOThread(), - /* simulate_slow_fetch */ true)); + new TestInMemoryProtocolHandler(browser()->BrowserIOThread(), this)); http_handler_ = http_handler.get(); http_handler_->InsertResponse("http://foo.com/index.html", {kRequestOrderTestPage, "text/html"}); @@ -991,27 +978,45 @@ class ResourceSchedulerTest : public HeadlessAsyncDevTooledBrowserTest, return protocol_handlers; } + void OnRequest(const GURL& url, base::Closure complete_request) override { + if (max_requests_in_flight_ < ++num_requests_in_flight_) + max_requests_in_flight_ = num_requests_in_flight_; + browser()->BrowserIOThread()->PostDelayedTask( + FROM_HERE, + base::Bind(&ResourceSchedulerTest::AllowRequest, base::Unretained(this), + complete_request), + base::TimeDelta::FromMilliseconds(100)); + } + + void AllowRequest(base::Closure complete_request) { + num_requests_in_flight_--; + complete_request.Run(); + } + const TestInMemoryProtocolHandler* http_handler() const { return http_handler_; } + int max_requests_in_flight() const { return max_requests_in_flight_; } + private: TestInMemoryProtocolHandler* http_handler_; // NOT OWNED + int num_requests_in_flight_ = 0; + int max_requests_in_flight_ = 0; }; -// TODO(alexclarke): Fix the flakes. http://crbug.com/766884 -class DISABLED_DisableResourceSchedulerTest : public ResourceSchedulerTest { +class DisableResourceSchedulerTest : public ResourceSchedulerTest { public: bool GetEnableResourceScheduler() override { return false; } void OnLoadEventFired(const page::LoadEventFiredParams&) override { - EXPECT_THAT(http_handler()->urls_requested(), - ElementsAreArray(kRequestOrderTestPageUrls)); + // All scripts should have been requested simultaneously. + EXPECT_EQ(20, max_requests_in_flight()); FinishAsynchronousTest(); } }; -HEADLESS_ASYNC_DEVTOOLED_TEST_F(DISABLED_DisableResourceSchedulerTest); +HEADLESS_ASYNC_DEVTOOLED_TEST_F(DisableResourceSchedulerTest); class EnableResourceSchedulerTest : public ResourceSchedulerTest { public: @@ -1020,12 +1025,8 @@ class EnableResourceSchedulerTest : public ResourceSchedulerTest { } void OnLoadEventFired(const page::LoadEventFiredParams&) override { - // We expect a different resource order when the ResourceScheduler is used. - EXPECT_THAT(http_handler()->urls_requested(), - Not(ElementsAreArray(kRequestOrderTestPageUrls))); - // However all the same urls should still be requested. - EXPECT_THAT(http_handler()->urls_requested(), - UnorderedElementsAreArray(kRequestOrderTestPageUrls)); + // Only a limited number of scripts should be requested simultaneously. + EXPECT_EQ(6, max_requests_in_flight()); FinishAsynchronousTest(); } }; @@ -1083,6 +1084,7 @@ class HeadlessWebContentsBeginFrameControlTest void SetUpCommandLine(base::CommandLine* command_line) override { HeadlessBrowserTest::SetUpCommandLine(command_line); command_line->AppendSwitch(cc::switches::kRunAllCompositorStagesBeforeDraw); + command_line->AppendSwitch(switches::kDisableNewContentRenderingTimeout); } void OnCreateTargetResult( @@ -1218,7 +1220,7 @@ class HeadlessWebContentsBeginFrameControlTest class HeadlessWebContentsBeginFrameControlBasicTest : public HeadlessWebContentsBeginFrameControlTest { public: - HeadlessWebContentsBeginFrameControlBasicTest() {} + HeadlessWebContentsBeginFrameControlBasicTest() = default; protected: std::string GetTestHtmlFile() override { @@ -1231,9 +1233,8 @@ class HeadlessWebContentsBeginFrameControlBasicTest void OnFrameFinished(std::unique_ptr<headless_experimental::BeginFrameResult> result) override { if (!sent_screenshot_request_) { - // Once no more BeginFrames are needed and the main frame is ready, - // capture a screenshot. - sent_screenshot_request_ = !needs_begin_frames_ && main_frame_ready_; + // Once the main frame is ready, capture a screenshot. + sent_screenshot_request_ = main_frame_ready_; BeginFrame(sent_screenshot_request_); } else { EXPECT_TRUE(result->GetHasDamage()); @@ -1262,11 +1263,10 @@ class HeadlessWebContentsBeginFrameControlBasicTest HEADLESS_ASYNC_DEVTOOLED_TEST_F(HeadlessWebContentsBeginFrameControlBasicTest); -// TODO(eseckler). class HeadlessWebContentsBeginFrameControlViewportTest : public HeadlessWebContentsBeginFrameControlTest { public: - HeadlessWebContentsBeginFrameControlViewportTest() {} + HeadlessWebContentsBeginFrameControlViewportTest() = default; protected: std::string GetTestHtmlFile() override { @@ -1279,16 +1279,10 @@ class HeadlessWebContentsBeginFrameControlViewportTest void OnFrameFinished(std::unique_ptr<headless_experimental::BeginFrameResult> result) override { if (!sent_screenshot_request_) { - // Once no more BeginFrames are needed and the main frame is ready, - // set the view size and position and then capture a screenshot. - if (!needs_begin_frames_ && main_frame_ready_) { - if (did_set_up_viewport_) { - // Finally, capture a screenshot. - sent_screenshot_request_ = true; - BeginFrame(true); - } else { - SetUpViewport(); - } + // Once the main frame is ready, set the view size and position and then + // capture a screenshot. + if (main_frame_ready_) { + SetUpViewport(); return; } @@ -1325,8 +1319,6 @@ class HeadlessWebContentsBeginFrameControlViewportTest } void SetUpViewport() { - did_set_up_viewport_ = 1; - // We should be needing BeginFrames again because of the viewport change. devtools_client_->GetEmulation() ->GetExperimental() ->SetDeviceMetricsOverride( @@ -1342,11 +1334,21 @@ class HeadlessWebContentsBeginFrameControlViewportTest .SetHeight(100) .SetScale(2) .Build()) - .Build()); + .Build(), + base::Bind(&HeadlessWebContentsBeginFrameControlViewportTest:: + SetDeviceMetricsOverrideDone, + base::Unretained(this))); + } + + void SetDeviceMetricsOverrideDone( + std::unique_ptr<emulation::SetDeviceMetricsOverrideResult> result) { + EXPECT_TRUE(result); + // Take a screenshot. + sent_screenshot_request_ = true; + BeginFrame(true); } bool sent_screenshot_request_ = false; - bool did_set_up_viewport_ = false; }; HEADLESS_ASYNC_DEVTOOLED_TEST_F( @@ -1354,4 +1356,74 @@ HEADLESS_ASYNC_DEVTOOLED_TEST_F( #endif // !defined(OS_MACOSX) +class CookiesEnabled : public HeadlessAsyncDevTooledBrowserTest, + page::Observer { + public: + void RunDevTooledTest() override { + devtools_client_->GetPage()->AddObserver(this); + devtools_client_->GetPage()->Enable(); + + EXPECT_TRUE(embedded_test_server()->Start()); + devtools_client_->GetPage()->Navigate( + embedded_test_server()->GetURL("/cookie.html").spec()); + } + + // page::Observer implementation: + void OnLoadEventFired(const page::LoadEventFiredParams& params) override { + devtools_client_->GetRuntime()->Evaluate( + "window.test_result", + base::Bind(&CookiesEnabled::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("0", value); + FinishAsynchronousTest(); + } + + void CustomizeHeadlessBrowserContext( + HeadlessBrowserContext::Builder& builder) override { + builder.SetAllowCookies(true); + } +}; + +HEADLESS_ASYNC_DEVTOOLED_TEST_F(CookiesEnabled); + +class CookiesDisabled : public HeadlessAsyncDevTooledBrowserTest, + page::Observer { + public: + void RunDevTooledTest() override { + devtools_client_->GetPage()->AddObserver(this); + devtools_client_->GetPage()->Enable(); + + EXPECT_TRUE(embedded_test_server()->Start()); + devtools_client_->GetPage()->Navigate( + embedded_test_server()->GetURL("/cookie.html").spec()); + } + + // page::Observer implementation: + void OnLoadEventFired(const page::LoadEventFiredParams& params) override { + devtools_client_->GetRuntime()->Evaluate( + "window.test_result", + base::Bind(&CookiesDisabled::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("-1", value); + FinishAsynchronousTest(); + } + + void CustomizeHeadlessBrowserContext( + HeadlessBrowserContext::Builder& builder) override { + builder.SetAllowCookies(false); + } +}; + +HEADLESS_ASYNC_DEVTOOLED_TEST_F(CookiesDisabled); + } // namespace headless diff --git a/chromium/headless/lib/renderer/headless_content_renderer_client.cc b/chromium/headless/lib/renderer/headless_content_renderer_client.cc index ce73e4d1ac9..edde26667f8 100644 --- a/chromium/headless/lib/renderer/headless_content_renderer_client.cc +++ b/chromium/headless/lib/renderer/headless_content_renderer_client.cc @@ -15,9 +15,9 @@ namespace headless { -HeadlessContentRendererClient::HeadlessContentRendererClient() {} +HeadlessContentRendererClient::HeadlessContentRendererClient() = default; -HeadlessContentRendererClient::~HeadlessContentRendererClient() {} +HeadlessContentRendererClient::~HeadlessContentRendererClient() = default; void HeadlessContentRendererClient::RenderFrameCreated( content::RenderFrame* 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 index b3dc8acf2dc..1b0995d8699 100644 --- a/chromium/headless/lib/renderer/headless_print_render_frame_helper_delegate.cc +++ b/chromium/headless/lib/renderer/headless_print_render_frame_helper_delegate.cc @@ -9,10 +9,10 @@ namespace headless { HeadlessPrintRenderFrameHelperDelegate:: - HeadlessPrintRenderFrameHelperDelegate() {} + HeadlessPrintRenderFrameHelperDelegate() = default; HeadlessPrintRenderFrameHelperDelegate:: - ~HeadlessPrintRenderFrameHelperDelegate() {} + ~HeadlessPrintRenderFrameHelperDelegate() = default; bool HeadlessPrintRenderFrameHelperDelegate::CancelPrerender( content::RenderFrame* render_frame) { @@ -37,10 +37,4 @@ 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 index 7e7b3136325..c23a7e98dfb 100644 --- a/chromium/headless/lib/renderer/headless_print_render_frame_helper_delegate.h +++ b/chromium/headless/lib/renderer/headless_print_render_frame_helper_delegate.h @@ -5,7 +5,7 @@ #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 "base/macros.h" #include "components/printing/renderer/print_render_frame_helper.h" namespace headless { @@ -23,9 +23,7 @@ class HeadlessPrintRenderFrameHelperDelegate bool IsAskPrintSettingsEnabled() override; blink::WebElement GetPdfElement(blink::WebLocalFrame* frame) override; -#if defined(OS_MACOSX) - bool UseSingleMetafile() override; -#endif + DISALLOW_COPY_AND_ASSIGN(HeadlessPrintRenderFrameHelperDelegate); }; } // namespace headless 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 b6761a6d361..9f695f69402 100644 --- a/chromium/headless/lib/renderer/headless_render_frame_controller_impl.cc +++ b/chromium/headless/lib/renderer/headless_render_frame_controller_impl.cc @@ -21,7 +21,8 @@ HeadlessRenderFrameControllerImpl::HeadlessRenderFrameControllerImpl( base::Unretained(this))); } -HeadlessRenderFrameControllerImpl::~HeadlessRenderFrameControllerImpl() {} +HeadlessRenderFrameControllerImpl::~HeadlessRenderFrameControllerImpl() = + default; void HeadlessRenderFrameControllerImpl::OnRenderFrameControllerRequest( HeadlessRenderFrameControllerRequest request) { diff --git a/chromium/headless/lib/renderer/headless_tab_socket_bindings.cc b/chromium/headless/lib/renderer/headless_tab_socket_bindings.cc index 94198ab19ab..247ed07012e 100644 --- a/chromium/headless/lib/renderer/headless_tab_socket_bindings.cc +++ b/chromium/headless/lib/renderer/headless_tab_socket_bindings.cc @@ -22,7 +22,7 @@ HeadlessTabSocketBindings::HeadlessTabSocketBindings( world_id_(world_id), installed_(false) {} -HeadlessTabSocketBindings::~HeadlessTabSocketBindings() {} +HeadlessTabSocketBindings::~HeadlessTabSocketBindings() = default; bool HeadlessTabSocketBindings::InitializeTabSocketBindings() { if (installed_) diff --git a/chromium/headless/lib/utility/headless_content_utility_client.cc b/chromium/headless/lib/utility/headless_content_utility_client.cc index a2ddaee3d87..d0e5f19d889 100644 --- a/chromium/headless/lib/utility/headless_content_utility_client.cc +++ b/chromium/headless/lib/utility/headless_content_utility_client.cc @@ -17,7 +17,7 @@ HeadlessContentUtilityClient::HeadlessContentUtilityClient( const std::string& user_agent) : user_agent_(user_agent) {} -HeadlessContentUtilityClient::~HeadlessContentUtilityClient() {} +HeadlessContentUtilityClient::~HeadlessContentUtilityClient() = default; void HeadlessContentUtilityClient::RegisterServices( HeadlessContentUtilityClient::StaticServiceMap* services) { diff --git a/chromium/headless/lib/virtual_time_browsertest.cc b/chromium/headless/lib/virtual_time_browsertest.cc index c4a5af7a012..7a0a6a6059f 100644 --- a/chromium/headless/lib/virtual_time_browsertest.cc +++ b/chromium/headless/lib/virtual_time_browsertest.cc @@ -3,17 +3,20 @@ // found in the LICENSE file. #include <memory> +#include "base/strings/string_number_conversions.h" #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" #include "headless/public/devtools/domains/runtime.h" #include "headless/public/headless_devtools_client.h" +#include "headless/public/util/testing/test_in_memory_protocol_handler.h" #include "headless/test/headless_browser_test.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" +using testing::ElementsAre; using testing::Contains; namespace headless { @@ -23,8 +26,11 @@ class VirtualTimeBrowserTest : public HeadlessAsyncDevTooledBrowserTest, public page::ExperimentalObserver, public runtime::Observer { public: + void SetInitialURL(const std::string& initial_url) { + initial_url_ = initial_url; + } + void RunDevTooledTest() override { - EXPECT_TRUE(embedded_test_server()->Start()); devtools_client_->GetEmulation()->GetExperimental()->AddObserver(this); devtools_client_->GetPage()->GetExperimental()->AddObserver(this); devtools_client_->GetRuntime()->AddObserver(this); @@ -45,7 +51,7 @@ class VirtualTimeBrowserTest : public HeadlessAsyncDevTooledBrowserTest, MaybeSetVirtualTimePolicy(); } - void MaybeSetVirtualTimePolicy() { + virtual void MaybeSetVirtualTimePolicy() { if (!page_enabled || !runtime_enabled) return; @@ -59,17 +65,17 @@ class VirtualTimeBrowserTest : public HeadlessAsyncDevTooledBrowserTest, } void SetVirtualTimePolicyDone( - std::unique_ptr<emulation::SetVirtualTimePolicyResult>) { + std::unique_ptr<emulation::SetVirtualTimePolicyResult> result) { + EXPECT_LT(0, result->GetVirtualTimeBase()); // Virtual time is paused, so start navigating. - devtools_client_->GetPage()->Navigate( - embedded_test_server()->GetURL("/virtual_time_test.html").spec()); + devtools_client_->GetPage()->Navigate(initial_url_); } void OnFrameStartedLoading( const page::FrameStartedLoadingParams& params) override { - if (intial_load_seen_) + if (initial_load_seen_) return; - intial_load_seen_ = true; + initial_load_seen_ = true; // The navigation is underway, so allow virtual time to advance while // network fetches are not pending. devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( @@ -86,11 +92,23 @@ class VirtualTimeBrowserTest : public HeadlessAsyncDevTooledBrowserTest, // We expect the arguments always to be a single string. const std::vector<std::unique_ptr<runtime::RemoteObject>>& args = *params.GetArgs(); - std::string message; - if (args.size() == 1u && args[0]->HasValue() && - args[0]->GetValue()->GetAsString(&message)) { - log_.push_back(message); - } + if (args.size() == 1u && args[0]->HasValue()) + log_.push_back(args[0]->GetValue()->GetString()); + } + + std::string initial_url_; + std::vector<std::string> log_; + bool initial_load_seen_ = false; + bool page_enabled = false; + bool runtime_enabled = false; +}; + +class VirtualTimeObserverTest : public VirtualTimeBrowserTest { + public: + VirtualTimeObserverTest() { + EXPECT_TRUE(embedded_test_server()->Start()); + SetInitialURL( + embedded_test_server()->GetURL("/virtual_time_test.html").spec()); } // emulation::Observer implementation: @@ -120,22 +138,465 @@ class VirtualTimeBrowserTest : public HeadlessAsyncDevTooledBrowserTest, void OnVirtualTimeAdvanced( const emulation::VirtualTimeAdvancedParams& params) override { - log_.push_back( - base::StringPrintf("Advanced to %dms", params.GetVirtualTimeElapsed())); + log_.push_back(base::StringPrintf("Advanced to %.fms", + params.GetVirtualTimeElapsed())); } void OnVirtualTimePaused( const emulation::VirtualTimePausedParams& params) override { log_.push_back( - base::StringPrintf("Paused @ %dms", params.GetVirtualTimeElapsed())); + base::StringPrintf("Paused @ %.fms", params.GetVirtualTimeElapsed())); } +}; - std::vector<std::string> log_; - bool intial_load_seen_ = false; - bool page_enabled = false; - bool runtime_enabled = false; +HEADLESS_ASYNC_DEVTOOLED_TEST_F(VirtualTimeObserverTest); + +class MaxVirtualTimeTaskStarvationCountTest : public VirtualTimeBrowserTest { + public: + MaxVirtualTimeTaskStarvationCountTest() { + EXPECT_TRUE(embedded_test_server()->Start()); + SetInitialURL(embedded_test_server() + ->GetURL("/virtual_time_starvation_test.html") + .spec()); + } + + void OnFrameStartedLoading( + const page::FrameStartedLoadingParams& params) override { + if (initial_load_seen_) + return; + initial_load_seen_ = true; + // The navigation is underway, so allow virtual time to advance while + // network fetches are not pending. + devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( + emulation::SetVirtualTimePolicyParams::Builder() + .SetPolicy( + emulation::VirtualTimePolicy::PAUSE_IF_NETWORK_FETCHES_PENDING) + .SetBudget(4000) + .SetMaxVirtualTimeTaskStarvationCount(100) + .Build()); + } + + // emulation::Observer implementation: + void OnVirtualTimeBudgetExpired( + const emulation::VirtualTimeBudgetExpiredParams& params) override { + // If SetMaxVirtualTimeTaskStarvationCount was not set, this callback would + // never fire. + FinishAsynchronousTest(); + } +}; + +HEADLESS_ASYNC_DEVTOOLED_TEST_F(MaxVirtualTimeTaskStarvationCountTest); + +namespace { +static constexpr char kIndexHtml[] = R"( +<html> +<body> +<iframe src="/iframe.html" width="400" height="200" id="iframe1"> +</iframe> +</body> +</html> +)"; + +static constexpr char kIFrame[] = R"( +<html> +<head> +<link rel="stylesheet" type="text/css" href="style.css"> +</head> +<body> +<h1>Hello from the iframe!</h1> +</body> +</html> +)"; + +static constexpr char kCss[] = R"( +.test { + color: blue; +} +)"; + +} // namespace + +class FrameDetatchWithPendingResourceLoadVirtualTimeTest + : public VirtualTimeBrowserTest, + public TestInMemoryProtocolHandler::RequestDeferrer { + public: + FrameDetatchWithPendingResourceLoadVirtualTimeTest() { + SetInitialURL("http://test.com/index.html"); + } + + ProtocolHandlerMap GetProtocolHandlers() override { + ProtocolHandlerMap protocol_handlers; + std::unique_ptr<TestInMemoryProtocolHandler> http_handler( + new TestInMemoryProtocolHandler(browser()->BrowserIOThread(), this)); + http_handler_ = http_handler.get(); + http_handler->InsertResponse("http://test.com/index.html", + {kIndexHtml, "text/html"}); + http_handler->InsertResponse("http://test.com/iframe.html", + {kIFrame, "text/html"}); + http_handler->InsertResponse("http://test.com/style.css", + {kCss, "text/css"}); + protocol_handlers[url::kHttpScheme] = std::move(http_handler); + return protocol_handlers; + } + + void RunDevTooledTest() override { + http_handler_->SetHeadlessBrowserContext(browser_context_); + VirtualTimeBrowserTest::RunDevTooledTest(); + } + + void OnRequest(const GURL& url, base::Closure complete_request) override { + // Note this is called on the IO thread. + if (url.spec() == "http://test.com/style.css") { + // Detach the iframe but leave the css resource fetch hanging. + browser()->BrowserMainThread()->PostTask( + FROM_HERE, + base::Bind(&FrameDetatchWithPendingResourceLoadVirtualTimeTest:: + DetatchIFrame, + base::Unretained(this))); + } else { + complete_request.Run(); + } + } + + void DetatchIFrame() { + devtools_client_->GetRuntime()->Evaluate( + "let elem = document.getElementById('iframe1');\n" + "elem.parentNode.removeChild(elem);"); + } + + // emulation::Observer implementation: + void OnVirtualTimeBudgetExpired( + const emulation::VirtualTimeBudgetExpiredParams& params) override { + EXPECT_THAT( + http_handler_->urls_requested(), + ElementsAre("http://test.com/index.html", "http://test.com/iframe.html", + "http://test.com/style.css")); + + // Virtual time should still expire, despite the CSS resource load not + // finishing. + FinishAsynchronousTest(); + } + + TestInMemoryProtocolHandler* http_handler_; // NOT OWNED +}; + +HEADLESS_ASYNC_DEVTOOLED_TEST_F( + FrameDetatchWithPendingResourceLoadVirtualTimeTest); + +class VirtualTimeLocalStorageTest : public VirtualTimeBrowserTest { + public: + VirtualTimeLocalStorageTest() { + EXPECT_TRUE(embedded_test_server()->Start()); + SetInitialURL(embedded_test_server() + ->GetURL("/virtual_time_local_storage.html") + .spec()); + } + + void OnFrameStartedLoading( + const page::FrameStartedLoadingParams& params) override { + if (initial_load_seen_) + return; + initial_load_seen_ = true; + // The navigation is underway, so allow virtual time to advance while + // network fetches are not pending. + devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( + emulation::SetVirtualTimePolicyParams::Builder() + .SetPolicy( + emulation::VirtualTimePolicy::PAUSE_IF_NETWORK_FETCHES_PENDING) + .SetBudget(4001) + .SetMaxVirtualTimeTaskStarvationCount(100) + .Build()); + } + + void OnConsoleAPICalled( + const runtime::ConsoleAPICalledParams& params) override { + EXPECT_EQ(runtime::ConsoleAPICalledType::LOG, params.GetType()); + ASSERT_EQ(1u, params.GetArgs()->size()); + ASSERT_EQ(runtime::RemoteObjectType::STRING, + (*params.GetArgs())[0]->GetType()); + std::string count_string = (*params.GetArgs())[0]->GetValue()->GetString(); + int count; + ASSERT_TRUE(base::StringToInt(count_string, &count)) << count_string; + // We don't care what exact number |count| has as long as it's not too small + // or too large. + EXPECT_GT(count, 100); + EXPECT_LT(count, 1000); + console_log_seen_ = true; + } + + // emulation::Observer implementation: + void OnVirtualTimeBudgetExpired( + const emulation::VirtualTimeBudgetExpiredParams& params) override { + // If SetMaxVirtualTimeTaskStarvationCount was not set, this callback would + // never fire. + EXPECT_TRUE(console_log_seen_); + FinishAsynchronousTest(); + } + + bool console_log_seen_ = false; +}; + +HEADLESS_ASYNC_DEVTOOLED_TEST_F(VirtualTimeLocalStorageTest); + +class VirtualTimeSessionStorageTest : public VirtualTimeBrowserTest { + public: + VirtualTimeSessionStorageTest() { + EXPECT_TRUE(embedded_test_server()->Start()); + SetInitialURL(embedded_test_server() + ->GetURL("/virtual_time_session_storage.html") + .spec()); + } + + void OnFrameStartedLoading( + const page::FrameStartedLoadingParams& params) override { + if (initial_load_seen_) + return; + initial_load_seen_ = true; + // The navigation is underway, so allow virtual time to advance while + // network fetches are not pending. + devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( + emulation::SetVirtualTimePolicyParams::Builder() + .SetPolicy( + emulation::VirtualTimePolicy::PAUSE_IF_NETWORK_FETCHES_PENDING) + .SetBudget(4001) + .SetMaxVirtualTimeTaskStarvationCount(100) + .Build()); + } + + void OnConsoleAPICalled( + const runtime::ConsoleAPICalledParams& params) override { + EXPECT_EQ(runtime::ConsoleAPICalledType::LOG, params.GetType()); + ASSERT_EQ(1u, params.GetArgs()->size()); + ASSERT_EQ(runtime::RemoteObjectType::STRING, + (*params.GetArgs())[0]->GetType()); + std::string count_string = (*params.GetArgs())[0]->GetValue()->GetString(); + int count; + ASSERT_TRUE(base::StringToInt(count_string, &count)) << count_string; + // We don't care what exact number |count| has as long as it's not too small + // or too large. + EXPECT_GT(count, 100); + EXPECT_LT(count, 1000); + console_log_seen_ = true; + } + + // emulation::Observer implementation: + void OnVirtualTimeBudgetExpired( + const emulation::VirtualTimeBudgetExpiredParams& params) override { + // If SetMaxVirtualTimeTaskStarvationCount was not set, this callback would + // never fire. + EXPECT_TRUE(console_log_seen_); + FinishAsynchronousTest(); + } + + bool console_log_seen_ = false; +}; + +HEADLESS_ASYNC_DEVTOOLED_TEST_F(VirtualTimeSessionStorageTest); + +class DeferredLoadDoesntBlockVirtualTimeTest : public VirtualTimeBrowserTest { + public: + DeferredLoadDoesntBlockVirtualTimeTest() { + EXPECT_TRUE(embedded_test_server()->Start()); + SetInitialURL(embedded_test_server()->GetURL("/video.html").spec()); + } + + // emulation::Observer implementation: + void OnVirtualTimeBudgetExpired( + const emulation::VirtualTimeBudgetExpiredParams& params) override { + // The video should not block virtual time. + FinishAsynchronousTest(); + } +}; + +HEADLESS_ASYNC_DEVTOOLED_TEST_F(DeferredLoadDoesntBlockVirtualTimeTest); + +class RedirectVirtualTimeTest : public VirtualTimeBrowserTest { + public: + RedirectVirtualTimeTest() { SetInitialURL("http://test.com/index.html"); } + + ProtocolHandlerMap GetProtocolHandlers() override { + ProtocolHandlerMap protocol_handlers; + std::unique_ptr<TestInMemoryProtocolHandler> http_handler( + new TestInMemoryProtocolHandler(browser()->BrowserIOThread(), nullptr)); + http_handler_ = http_handler.get(); + http_handler->InsertResponse("http://test.com/index.html", + {kIndexHtml, "text/html"}); + http_handler->InsertResponse( + "http://test.com/iframe.html", + {"HTTP/1.1 302 Found\r\nLocation: iframe2.html\r\n\r\n"}); + http_handler->InsertResponse("http://test.com/iframe2.html", + {kIFrame, "text/html"}); + http_handler->InsertResponse( + "http://test.com/style.css", + {"HTTP/1.1 302 Found\r\nLocation: style2.css\r\n\r\n"}); + http_handler->InsertResponse("http://test.com/style2.css", + {kCss, "text/css"}); + protocol_handlers[url::kHttpScheme] = std::move(http_handler); + return protocol_handlers; + } + + void RunDevTooledTest() override { + http_handler_->SetHeadlessBrowserContext(browser_context_); + VirtualTimeBrowserTest::RunDevTooledTest(); + } + + // emulation::Observer implementation: + void OnVirtualTimeBudgetExpired( + const emulation::VirtualTimeBudgetExpiredParams& params) override { + EXPECT_THAT( + http_handler_->urls_requested(), + ElementsAre("http://test.com/index.html", "http://test.com/iframe.html", + "http://test.com/iframe2.html", "http://test.com/style.css", + "http://test.com/style2.css")); + + // Virtual time should still expire, despite the CSS resource load not + // finishing. + FinishAsynchronousTest(); + } + + TestInMemoryProtocolHandler* http_handler_; // NOT OWNED +}; + +HEADLESS_ASYNC_DEVTOOLED_TEST_F(RedirectVirtualTimeTest); + +namespace { +static constexpr char kFooDotCom[] = R"( +<html> +<body> +<script> console.log(document.location.href); </script> +<iframe src='/a/'></iframe>\n" +</body> +</html> +)"; + +static constexpr char kFooDotComSlashA[] = R"( +<html> +<body> +<script> console.log(document.location.href); </script> +</body> +</html> +)"; + +static constexpr char kBarDotCom[] = R"( +<html> +<body> +<script> console.log(document.location.href); </script> +<iframe src='/b/' id='frame_b'></iframe> +<iframe src='/c/'></iframe> +</body> +</html> +)"; + +static constexpr char kBarDotComSlashB[] = R"( +<html> +<body> +<script> console.log(document.location.href); </script> +<iframe src='/d/'></iframe> +</body> +</html> +)"; + +static constexpr char kBarDotComSlashC[] = R"( +<html> +<body> +<script> console.log(document.location.href); </script> +</body> +</html> +)"; + +static constexpr char kBarDotComSlashD[] = R"( +<html> +<body> +<script> console.log(document.location.href); </script> +</body> +</html> +)"; + +static constexpr char kBarDotComSlashE[] = R"( +<html> +<body> +<script> console.log(document.location.href); </script> +<iframe src='/f/'></iframe> +</body> +</html> +)"; + +static constexpr char kBarDotComSlashF[] = R"( +<html> +<body> +<script> console.log(document.location.href); </script> +</body> +</html> +)"; + +} // namespace + +class VirtualTimeAndHistoryNavigationTest : public VirtualTimeBrowserTest { + public: + VirtualTimeAndHistoryNavigationTest() { SetInitialURL("http://foo.com/"); } + + ProtocolHandlerMap GetProtocolHandlers() override { + ProtocolHandlerMap protocol_handlers; + std::unique_ptr<TestInMemoryProtocolHandler> http_handler( + new TestInMemoryProtocolHandler(browser()->BrowserIOThread(), nullptr)); + http_handler_ = http_handler.get(); + http_handler->InsertResponse("http://foo.com/", {kFooDotCom, "text/html"}); + http_handler->InsertResponse("http://foo.com/a/", + {kFooDotComSlashA, "text/html"}); + http_handler->InsertResponse("http://bar.com/", {kBarDotCom, "text/html"}); + http_handler->InsertResponse("http://bar.com/b/", + {kBarDotComSlashB, "text/html"}); + http_handler->InsertResponse("http://bar.com/c/", + {kBarDotComSlashC, "text/html"}); + http_handler->InsertResponse("http://bar.com/d/", + {kBarDotComSlashD, "text/html"}); + http_handler->InsertResponse("http://bar.com/e/", + {kBarDotComSlashE, "text/html"}); + http_handler->InsertResponse("http://bar.com/f/", + {kBarDotComSlashF, "text/html"}); + protocol_handlers[url::kHttpScheme] = std::move(http_handler); + return protocol_handlers; + } + + void RunDevTooledTest() override { + http_handler_->SetHeadlessBrowserContext(browser_context_); + VirtualTimeBrowserTest::RunDevTooledTest(); + } + + // emulation::Observer implementation: + void OnVirtualTimeBudgetExpired( + const emulation::VirtualTimeBudgetExpiredParams& params) override { + if (step_ < test_commands_.size()) { + devtools_client_->GetRuntime()->Evaluate( + test_commands_[step_++], + base::Bind(&VirtualTimeAndHistoryNavigationTest::OnEvaluateResult, + base::Unretained(this))); + } else { + FinishAsynchronousTest(); + } + } + + void OnEvaluateResult(std::unique_ptr<runtime::EvaluateResult>) { + devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( + emulation::SetVirtualTimePolicyParams::Builder() + .SetPolicy( + emulation::VirtualTimePolicy::PAUSE_IF_NETWORK_FETCHES_PENDING) + .SetBudget(5000) + .Build()); + } + + const std::vector<std::string> test_commands_ = { + "document.location.href = 'http://bar.com/'", + "document.getElementById('frame_b').src = '/e/'", + "history.back()", + "history.forward()", + "history.go(-1)", + }; + + size_t step_ = 0; + TestInMemoryProtocolHandler* http_handler_; // NOT OWNED }; -HEADLESS_ASYNC_DEVTOOLED_TEST_F(VirtualTimeBrowserTest); +HEADLESS_ASYNC_DEVTOOLED_TEST_F(VirtualTimeAndHistoryNavigationTest); } // namespace headless diff --git a/chromium/headless/public/headless_browser.cc b/chromium/headless/public/headless_browser.cc index 1f01f5a3f87..7e124fc32b4 100644 --- a/chromium/headless/public/headless_browser.cc +++ b/chromium/headless/public/headless_browser.cc @@ -31,16 +31,6 @@ std::string GetProductNameAndVersion() { Options::Options(int argc, const char** argv) : argc(argc), argv(argv), -#if defined(OS_WIN) - instance(0), - sandbox_info(nullptr), -#endif - devtools_endpoint(), - devtools_socket_fd(0), - message_pump(nullptr), - single_process_mode(false), - disable_sandbox(false), - enable_resource_scheduler(true), #if defined(USE_OZONE) // TODO(skyostil): Implement SwiftShader backend for headless ozone. gl_implementation("osmesa"), @@ -54,14 +44,12 @@ Options::Options(int argc, const char** argv) #endif product_name_and_version(GetProductNameAndVersion()), user_agent(content::BuildUserAgentFromProduct(product_name_and_version)), - window_size(kDefaultWindowSize), - incognito_mode(true), - enable_crash_reporter(false) { + window_size(kDefaultWindowSize) { } Options::Options(Options&& options) = default; -Options::~Options() {} +Options::~Options() = default; Options& Options::operator=(Options&& options) = default; @@ -73,7 +61,7 @@ Builder::Builder(int argc, const char** argv) : options_(argc, argv) {} Builder::Builder() : options_(0, nullptr) {} -Builder::~Builder() {} +Builder::~Builder() = default; Builder& Builder::SetProductNameAndVersion( const std::string& product_name_and_version) { @@ -142,6 +130,12 @@ Builder& Builder::AddMojoServiceName(const std::string& mojo_service_name) { return *this; } +Builder& Builder::SetAppendCommandLineFlagsCallback( + const Options::AppendCommandLineFlagsCallback& callback) { + options_.append_command_line_flags_callback = callback; + return *this; +} + #if defined(OS_WIN) Builder& Builder::SetInstance(HINSTANCE instance) { options_.instance = instance; @@ -170,7 +164,7 @@ Builder& Builder::SetIncognitoMode(bool incognito_mode) { } Builder& Builder::SetOverrideWebPreferencesCallback( - base::Callback<void(WebPreferences*)> callback) { + const base::Callback<void(WebPreferences*)>& callback) { options_.override_web_preferences_callback = callback; return *this; } diff --git a/chromium/headless/public/headless_browser.h b/chromium/headless/public/headless_browser.h index d3ef26dcd76..3ce4f82a72c 100644 --- a/chromium/headless/public/headless_browser.h +++ b/chromium/headless/public/headless_browser.h @@ -12,6 +12,7 @@ #include <vector> #include "base/callback.h" +#include "base/command_line.h" #include "base/files/file_path.h" #include "base/macros.h" #include "base/memory/ref_counted.h" @@ -102,16 +103,16 @@ struct HEADLESS_EXPORT HeadlessBrowser::Options { Options& operator=(Options&& options); - // Command line options to be passed to browser. + // Command line options to be passed to browser. Initialized in constructor. int argc; const char** argv; #if defined(OS_WIN) // Set hardware instance if available, otherwise it defaults to 0. - HINSTANCE instance; + HINSTANCE instance = 0; // Set with sandbox information. This has to be already initialized. - sandbox::SandboxInterfaceInfo* sandbox_info; + sandbox::SandboxInterfaceInfo* sandbox_info = nullptr; #endif // Address at which DevTools should listen for connections. Disabled by @@ -120,25 +121,25 @@ struct HEADLESS_EXPORT HeadlessBrowser::Options { // The fd of an already-open socket inherited from a parent process. Disabled // by default. Mutually exclusive with devtools_endpoint. - size_t devtools_socket_fd; + size_t devtools_socket_fd = 0; // A single way to test whether the devtools server has been requested. bool DevtoolsServerEnabled(); // Optional message pump that overrides the default. Must outlive the browser. - base::MessagePump* message_pump; + base::MessagePump* message_pump = nullptr; // Run the browser in single process mode instead of using separate renderer // processes as per default. Note that this also disables any sandboxing of // web content, which can be a security risk. - bool single_process_mode; + bool single_process_mode = false; // Run the browser without renderer sandbox. This option can be // a security risk and should be used with caution. - bool disable_sandbox; + bool disable_sandbox = false; // Whether or not to enable content::ResourceScheduler. Enabled by default. - bool enable_resource_scheduler; + bool enable_resource_scheduler = true; // Choose the GL implementation to use for rendering. A suitable // implementantion is selected by default. Setting this to an empty @@ -156,7 +157,7 @@ struct HEADLESS_EXPORT HeadlessBrowser::Options { std::string user_agent; // The ProxyConfig to use. The system proxy settings are used by default. - std::unique_ptr<net::ProxyConfig> proxy_config; + std::unique_ptr<net::ProxyConfig> proxy_config = nullptr; // Comma-separated list of rules that control how hostnames are mapped. See // chrome::switches::kHostRules for a description for the format. @@ -171,7 +172,10 @@ struct HEADLESS_EXPORT HeadlessBrowser::Options { base::FilePath user_data_dir; // Run a browser context in an incognito mode. Enabled by default. - bool incognito_mode; + bool incognito_mode = true; + + // Whether cookies are allowed. Enabled by default. + bool allow_cookies = true; // Set a callback that is invoked to override WebPreferences for RenderViews // created within the HeadlessBrowser. Called whenever the WebPreferences of a @@ -181,10 +185,25 @@ struct HEADLESS_EXPORT HeadlessBrowser::Options { // exposed WebPreferences API, so use with care. base::Callback<void(WebPreferences*)> override_web_preferences_callback; + // Set a callback that is invoked when a new child process is spawned or + // forked and allows adding additional command line flags to the child + // process's command line. Executed on the browser main thread. + // |child_browser_context| points to the BrowserContext of the child + // process, but will only be set if the child process is a renderer process. + // + // NOTE: This callback may be called on the UI or IO thread even after the + // HeadlessBrowser has been destroyed. + using AppendCommandLineFlagsCallback = + base::Callback<void(base::CommandLine* command_line, + HeadlessBrowserContext* child_browser_context, + const std::string& child_process_type, + int child_process_id)>; + AppendCommandLineFlagsCallback append_command_line_flags_callback; + // Minidump crash reporter settings. Crash reporting is disabled by default. // By default crash dumps are written to the directory containing the // executable. - bool enable_crash_reporter; + bool enable_crash_reporter = false; base::FilePath crash_dumps_dir; // Reminder: when adding a new field here, do not forget to add it to @@ -211,6 +230,8 @@ class HEADLESS_EXPORT HeadlessBrowser::Options::Builder { Builder& SetEnableResourceScheduler(bool enable_resource_scheduler); Builder& SetGLImplementation(const std::string& gl_implementation); Builder& AddMojoServiceName(const std::string& mojo_service_name); + Builder& SetAppendCommandLineFlagsCallback( + const Options::AppendCommandLineFlagsCallback& callback); #if defined(OS_WIN) Builder& SetInstance(HINSTANCE instance); Builder& SetSandboxInfo(sandbox::SandboxInterfaceInfo* sandbox_info); @@ -227,8 +248,9 @@ class HEADLESS_EXPORT HeadlessBrowser::Options::Builder { Builder& SetWindowSize(const gfx::Size& window_size); Builder& SetUserDataDir(const base::FilePath& user_data_dir); Builder& SetIncognitoMode(bool incognito_mode); + Builder& SetAllowCookies(bool allow_cookies); Builder& SetOverrideWebPreferencesCallback( - base::Callback<void(WebPreferences*)> callback); + const base::Callback<void(WebPreferences*)>& callback); Builder& SetCrashReporterEnabled(bool enabled); Builder& SetCrashDumpsDir(const base::FilePath& dir); diff --git a/chromium/headless/public/headless_browser_context.h b/chromium/headless/public/headless_browser_context.h index 83b1207ea52..1d117decfa6 100644 --- a/chromium/headless/public/headless_browser_context.h +++ b/chromium/headless/public/headless_browser_context.h @@ -14,6 +14,7 @@ #include "base/callback.h" #include "base/optional.h" #include "content/public/common/web_preferences.h" +#include "headless/lib/browser/headless_network_conditions.h" #include "headless/public/headless_export.h" #include "headless/public/headless_web_contents.h" #include "net/proxy/proxy_service.h" @@ -69,6 +70,8 @@ class HEADLESS_EXPORT HeadlessBrowserContext { virtual void AddObserver(Observer* observer) = 0; virtual void RemoveObserver(Observer* observer) = 0; + virtual HeadlessNetworkConditions GetNetworkConditions() = 0; + // TODO(skyostil): Allow saving and restoring contexts (crbug.com/617931). protected: @@ -132,6 +135,7 @@ class HEADLESS_EXPORT HeadlessBrowserContext::Builder { Builder& SetWindowSize(const gfx::Size& window_size); Builder& SetUserDataDir(const base::FilePath& user_data_dir); Builder& SetIncognitoMode(bool incognito_mode); + Builder& SetAllowCookies(bool incognito_mode); Builder& SetOverrideWebPreferencesCallback( base::Callback<void(WebPreferences*)> callback); diff --git a/chromium/headless/public/headless_devtools_client.h b/chromium/headless/public/headless_devtools_client.h index 88b11826ee3..ee4a336f25f 100644 --- a/chromium/headless/public/headless_devtools_client.h +++ b/chromium/headless/public/headless_devtools_client.h @@ -10,6 +10,10 @@ #include "base/macros.h" #include "headless/public/headless_export.h" +namespace base { +class DictionaryValue; +} // namespace base + namespace headless { namespace accessibility { @@ -90,6 +94,9 @@ class Domain; namespace page { class Domain; } +namespace performance { +class Domain; +} namespace profiler { class Domain; } @@ -147,6 +154,7 @@ class HEADLESS_EXPORT HeadlessDevToolsClient { virtual memory::Domain* GetMemory() = 0; virtual network::Domain* GetNetwork() = 0; virtual page::Domain* GetPage() = 0; + virtual performance::Domain* GetPerformance() = 0; virtual profiler::Domain* GetProfiler() = 0; virtual runtime::Domain* GetRuntime() = 0; virtual security::Domain* GetSecurity() = 0; diff --git a/chromium/headless/public/internal/headless_devtools_client_impl.h b/chromium/headless/public/internal/headless_devtools_client_impl.h index ed1eb70efcd..d9b89eae511 100644 --- a/chromium/headless/public/internal/headless_devtools_client_impl.h +++ b/chromium/headless/public/internal/headless_devtools_client_impl.h @@ -36,6 +36,7 @@ #include "headless/public/devtools/domains/memory.h" #include "headless/public/devtools/domains/network.h" #include "headless/public/devtools/domains/page.h" +#include "headless/public/devtools/domains/performance.h" #include "headless/public/devtools/domains/profiler.h" #include "headless/public/devtools/domains/runtime.h" #include "headless/public/devtools/domains/security.h" @@ -93,6 +94,7 @@ class HEADLESS_EXPORT HeadlessDevToolsClientImpl memory::Domain* GetMemory() override; network::Domain* GetNetwork() override; page::Domain* GetPage() override; + performance::Domain* GetPerformance() override; profiler::Domain* GetProfiler() override; runtime::Domain* GetRuntime() override; security::Domain* GetSecurity() override; @@ -105,11 +107,10 @@ class HEADLESS_EXPORT HeadlessDevToolsClientImpl void SendRawDevToolsMessage(const std::string& json_message) override; void SendRawDevToolsMessage(const base::DictionaryValue& message) override; - // content::DevToolstAgentHostClient implementation: + // content::DevToolsAgentHostClient implementation: void DispatchProtocolMessage(content::DevToolsAgentHost* agent_host, const std::string& json_message) override; - void AgentHostClosed(content::DevToolsAgentHost* agent_host, - bool replaced_with_another_client) override; + void AgentHostClosed(content::DevToolsAgentHost* agent_host) override; // internal::MessageDispatcher implementation: void SendMessage(const char* method, @@ -203,6 +204,7 @@ class HEADLESS_EXPORT HeadlessDevToolsClientImpl memory::ExperimentalDomain memory_domain_; network::ExperimentalDomain network_domain_; page::ExperimentalDomain page_domain_; + performance::ExperimentalDomain performance_domain_; profiler::ExperimentalDomain profiler_domain_; runtime::ExperimentalDomain runtime_domain_; security::ExperimentalDomain security_domain_; diff --git a/chromium/headless/public/internal/value_conversions.h b/chromium/headless/public/internal/value_conversions.h index f2ded264939..7f218b42da9 100644 --- a/chromium/headless/public/internal/value_conversions.h +++ b/chromium/headless/public/internal/value_conversions.h @@ -74,40 +74,44 @@ std::unique_ptr<base::Value> ToValueImpl(const std::unique_ptr<T>& value, template <> struct FromValue<bool> { static bool Parse(const base::Value& value, ErrorReporter* errors) { - bool result = false; - if (!value.GetAsBoolean(&result)) + if (!value.is_bool()) { errors->AddError("boolean value expected"); - return result; + return false; + } + return value.GetBool(); } }; template <> struct FromValue<int> { static int Parse(const base::Value& value, ErrorReporter* errors) { - int result = 0; - if (!value.GetAsInteger(&result)) + if (!value.is_int()) { errors->AddError("integer value expected"); - return result; + return 0; + } + return value.GetInt(); } }; template <> struct FromValue<double> { static double Parse(const base::Value& value, ErrorReporter* errors) { - double result = 0; - if (!value.GetAsDouble(&result)) + if (!value.is_double() && !value.is_int()) { errors->AddError("double value expected"); - return result; + return 0; + } + return value.GetDouble(); } }; template <> struct FromValue<std::string> { static std::string Parse(const base::Value& value, ErrorReporter* errors) { - std::string result; - if (!value.GetAsString(&result)) + if (!value.is_string()) { errors->AddError("string value expected"); - return result; + return ""; + } + return value.GetString(); } }; @@ -144,13 +148,12 @@ template <typename T> struct FromValue<std::vector<T>> { static std::vector<T> Parse(const base::Value& value, ErrorReporter* errors) { std::vector<T> result; - const base::ListValue* list; - if (!value.GetAsList(&list)) { + if (!value.is_list()) { errors->AddError("list value expected"); return result; } errors->Push(); - for (const auto& item : *list) + for (const auto& item : value.GetList()) result.push_back(FromValue<T>::Parse(item, errors)); errors->Pop(); return result; diff --git a/chromium/headless/public/util/DEPS b/chromium/headless/public/util/DEPS new file mode 100644 index 00000000000..5fc6a7b9a6a --- /dev/null +++ b/chromium/headless/public/util/DEPS @@ -0,0 +1,6 @@ +specific_include_rules = { + "compositor_controller_browsertest.cc": [ + "+cc/base/switches.h", + ] +} + diff --git a/chromium/headless/public/util/black_hole_protocol_handler.cc b/chromium/headless/public/util/black_hole_protocol_handler.cc index 3f036c59706..31a269b3328 100644 --- a/chromium/headless/public/util/black_hole_protocol_handler.cc +++ b/chromium/headless/public/util/black_hole_protocol_handler.cc @@ -34,7 +34,7 @@ BlackHoleRequestJob::BlackHoleRequestJob(net::URLRequest* request, net::NetworkDelegate* network_delegate) : net::URLRequestJob(request, network_delegate), weak_factory_(this) {} -BlackHoleRequestJob::~BlackHoleRequestJob() {} +BlackHoleRequestJob::~BlackHoleRequestJob() = default; void BlackHoleRequestJob::Start() { base::ThreadTaskRunnerHandle::Get()->PostTask( @@ -54,8 +54,8 @@ void BlackHoleRequestJob::Kill() { } } // namespace -BlackHoleProtocolHandler::BlackHoleProtocolHandler() {} -BlackHoleProtocolHandler::~BlackHoleProtocolHandler() {} +BlackHoleProtocolHandler::BlackHoleProtocolHandler() = default; +BlackHoleProtocolHandler::~BlackHoleProtocolHandler() = default; net::URLRequestJob* BlackHoleProtocolHandler::MaybeCreateJob( net::URLRequest* request, diff --git a/chromium/headless/public/util/compositor_controller.cc b/chromium/headless/public/util/compositor_controller.cc new file mode 100644 index 00000000000..2bc803c8532 --- /dev/null +++ b/chromium/headless/public/util/compositor_controller.cc @@ -0,0 +1,429 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "headless/public/util/compositor_controller.h" + +#include "base/base64.h" +#include "base/bind.h" +#include "base/cancelable_callback.h" +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/trace_event/trace_event.h" +#include "headless/public/util/virtual_time_controller.h" + +namespace headless { + +// Sends BeginFrames to advance animations while virtual time advances in +// intervals. +class CompositorController::AnimationBeginFrameTask + : public VirtualTimeController::RepeatingTask { + public: + explicit AnimationBeginFrameTask(CompositorController* compositor_controller) + : compositor_controller_(compositor_controller), + weak_ptr_factory_(this) {} + + // VirtualTimeController::RepeatingTask implementation: + void IntervalElapsed(base::TimeDelta virtual_time_offset, + const base::Closure& continue_callback) override { + continue_callback_ = continue_callback; + + // Post a cancellable task that will issue a BeginFrame. This way, we can + // cancel sending an animation-only BeginFrame if another virtual time task + // sends a screenshotting BeginFrame first, or if the budget was exhausted. + // TODO(eseckler): This won't capture screenshot requests sent + // asynchronously. + begin_frame_task_.Reset( + base::Bind(&AnimationBeginFrameTask::IssueAnimationBeginFrame, + weak_ptr_factory_.GetWeakPtr())); + compositor_controller_->task_runner_->PostTask( + FROM_HERE, begin_frame_task_.callback()); + } + + void BudgetRequested(base::TimeDelta virtual_time_offset, + base::TimeDelta requested_budget, + const base::Closure& continue_callback) override { + // Run a BeginFrame if we cancelled it because the budged expired previously + // and no other BeginFrame was sent while virtual time was paused. + if (needs_begin_frame_on_virtual_time_resume_) { + continue_callback_ = continue_callback; + IssueAnimationBeginFrame(); + return; + } + continue_callback.Run(); + } + + void BudgetExpired(base::TimeDelta virtual_time_offset) override { + // Wait until a new budget was requested before sending another animation + // BeginFrame, as it's likely that we will send a screenshotting BeginFrame. + if (!begin_frame_task_.IsCancelled()) { + begin_frame_task_.Cancel(); + needs_begin_frame_on_virtual_time_resume_ = true; + BeginFrameComplete(nullptr); + } + } + + void CompositorControllerIssuingScreenshotBeginFrame() { + // The screenshotting BeginFrame will replace our animation-only BeginFrame. + // We cancel any pending animation BeginFrame to avoid sending two + // BeginFrames within the same virtual time pause. + needs_begin_frame_on_virtual_time_resume_ = false; + if (!begin_frame_task_.IsCancelled()) { + begin_frame_task_.Cancel(); + BeginFrameComplete(nullptr); + } + } + + private: + void IssueAnimationBeginFrame() { + TRACE_EVENT0("headless", + "CompositorController::AnimationBeginFrameTask::" + "IssueAnimationBeginFrame"); + begin_frame_task_.Cancel(); + needs_begin_frame_on_virtual_time_resume_ = false; + // No need for PostBeginFrame, since the begin_frame_task_ has already been + // posted above. + compositor_controller_->BeginFrame( + base::Bind(&AnimationBeginFrameTask::BeginFrameComplete, + weak_ptr_factory_.GetWeakPtr())); + } + + void BeginFrameComplete(std::unique_ptr<BeginFrameResult>) { + TRACE_EVENT0( + "headless", + "CompositorController::AnimationBeginFrameTask::BeginFrameComplete"); + DCHECK(continue_callback_); + auto callback = continue_callback_; + continue_callback_.Reset(); + callback.Run(); + } + + CompositorController* compositor_controller_; // NOT OWNED + bool needs_begin_frame_on_virtual_time_resume_ = false; + base::CancelableClosure begin_frame_task_; + base::Closure continue_callback_; + base::WeakPtrFactory<AnimationBeginFrameTask> weak_ptr_factory_; +}; + +CompositorController::CompositorController( + scoped_refptr<base::SequencedTaskRunner> task_runner, + HeadlessDevToolsClient* devtools_client, + VirtualTimeController* virtual_time_controller, + base::TimeDelta animation_begin_frame_interval, + base::TimeDelta wait_for_compositor_ready_begin_frame_delay) + : task_runner_(std::move(task_runner)), + devtools_client_(devtools_client), + virtual_time_controller_(virtual_time_controller), + animation_task_(base::MakeUnique<AnimationBeginFrameTask>(this)), + animation_begin_frame_interval_(animation_begin_frame_interval), + wait_for_compositor_ready_begin_frame_delay_( + wait_for_compositor_ready_begin_frame_delay), + weak_ptr_factory_(this) { + devtools_client_->GetHeadlessExperimental()->GetExperimental()->AddObserver( + this); + // No need to wait for completion of this, since we are waiting for the + // setNeedsBeginFramesChanged event instead, which will be sent at some point + // after enabling the domain. + devtools_client_->GetHeadlessExperimental()->GetExperimental()->Enable( + headless_experimental::EnableParams::Builder().Build()); + virtual_time_controller_->ScheduleRepeatingTask( + animation_task_.get(), animation_begin_frame_interval_); +} + +CompositorController::~CompositorController() { + virtual_time_controller_->CancelRepeatingTask(animation_task_.get()); + devtools_client_->GetHeadlessExperimental() + ->GetExperimental() + ->RemoveObserver(this); +} + +void CompositorController::PostBeginFrame( + const base::Callback<void(std::unique_ptr<BeginFrameResult>)>& + begin_frame_complete_callback, + std::unique_ptr<ScreenshotParams> screenshot) { + // In certain nesting situations, we should not issue a BeginFrame immediately + // - for example, issuing a new BeginFrame within a BeginFrameCompleted or + // NeedsBeginFramesChanged event can upset the compositor. We avoid these + // situations by issuing our BeginFrames from a separately posted task. + task_runner_->PostTask( + FROM_HERE, + base::Bind(&CompositorController::BeginFrame, + weak_ptr_factory_.GetWeakPtr(), begin_frame_complete_callback, + base::Passed(&screenshot))); +} + +void CompositorController::BeginFrame( + const base::Callback<void(std::unique_ptr<BeginFrameResult>)>& + begin_frame_complete_callback, + std::unique_ptr<ScreenshotParams> screenshot) { + DCHECK(!begin_frame_complete_callback_); + begin_frame_complete_callback_ = begin_frame_complete_callback; + if (needs_begin_frames_ || screenshot) { + auto params_builder = headless_experimental::BeginFrameParams::Builder(); + + // Use virtual time for frame time, so that rendering of animations etc. is + // aligned with virtual time progression. + base::Time frame_time = virtual_time_controller_->GetCurrentVirtualTime(); + if (frame_time <= last_begin_frame_time_) { + // Frame time cannot go backwards or stop, so we issue another BeginFrame + // with a small time offset from the last BeginFrame's time instead. + frame_time = + last_begin_frame_time_ + base::TimeDelta::FromMicroseconds(1); + } + params_builder.SetFrameTime(frame_time.ToJsTime()); + DCHECK_GT(frame_time, last_begin_frame_time_); + DCHECK_GT(frame_time.ToJsTime(), last_begin_frame_time_.ToJsTime()); + last_begin_frame_time_ = frame_time; + + params_builder.SetInterval( + animation_begin_frame_interval_.InMillisecondsF()); + + // TODO(eseckler): Set time fields. This requires obtaining the absolute + // virtual time stamp. + if (screenshot) + params_builder.SetScreenshot(std::move(screenshot)); + + devtools_client_->GetHeadlessExperimental()->GetExperimental()->BeginFrame( + params_builder.Build(), + base::Bind(&CompositorController::BeginFrameComplete, + weak_ptr_factory_.GetWeakPtr())); + } else { + BeginFrameComplete(nullptr); + } +} + +void CompositorController::BeginFrameComplete( + std::unique_ptr<BeginFrameResult> result) { + DCHECK(begin_frame_complete_callback_); + auto callback = begin_frame_complete_callback_; + begin_frame_complete_callback_.Reset(); + callback.Run(std::move(result)); + if (idle_callback_) { + auto idle_callback = idle_callback_; + idle_callback_.Reset(); + idle_callback.Run(); + } +} + +void CompositorController::OnNeedsBeginFramesChanged( + const NeedsBeginFramesChangedParams& params) { + needs_begin_frames_ = params.GetNeedsBeginFrames(); + + // If needs_begin_frames_ became true again and we're waiting for the + // compositor or a main frame update, continue posting BeginFrames - provided + // there's none outstanding. + if (compositor_ready_callback_ && needs_begin_frames_ && + !begin_frame_complete_callback_ && + wait_for_compositor_ready_begin_frame_task_.IsCancelled()) { + PostBeginFrame(base::Bind( + &CompositorController::WaitForCompositorReadyBeginFrameComplete, + weak_ptr_factory_.GetWeakPtr())); + } else if (main_frame_content_updated_callback_ && needs_begin_frames_ && + !begin_frame_complete_callback_) { + PostWaitForMainFrameContentUpdateBeginFrame(); + } +} + +void CompositorController::OnMainFrameReadyForScreenshots( + const MainFrameReadyForScreenshotsParams& params) { + main_frame_ready_ = true; + + // If a WaitForCompositorReadyBeginFrame is still scheduled, skip it. + if (!wait_for_compositor_ready_begin_frame_task_.IsCancelled()) { + wait_for_compositor_ready_begin_frame_task_.Cancel(); + auto callback = compositor_ready_callback_; + compositor_ready_callback_.Reset(); + callback.Run(); + } +} + +void CompositorController::WaitForCompositorReady( + const base::Closure& compositor_ready_callback) { + // We need to wait for the mainFrameReadyForScreenshots event, which will be + // issued once the renderer has submitted its first CompositorFrame in + // response to a BeginFrame. At that point, we know that the renderer + // compositor has initialized. We do this by issuing BeginFrames until we + // receive the event. To avoid bogging down the system with a flood of + // BeginFrames, we add a short delay between them. + // TODO(eseckler): Investigate if we can remove the need for these initial + // BeginFrames and the mainFrameReadyForScreenshots event, by making the + // compositor wait for the renderer in the very first BeginFrame, even if it + // isn't yet present in the surface hierarchy. Maybe surface synchronization + // can help here? + DCHECK(!begin_frame_complete_callback_); + DCHECK(!compositor_ready_callback_); + + if (main_frame_ready_) { + compositor_ready_callback.Run(); + return; + } + + compositor_ready_callback_ = compositor_ready_callback; + if (needs_begin_frames_) { + // Post BeginFrames with a delay until the main frame becomes ready. + PostWaitForCompositorReadyBeginFrameTask(); + } +} + +void CompositorController::PostWaitForCompositorReadyBeginFrameTask() { + TRACE_EVENT0( + "headless", + "CompositorController::PostWaitForCompositorReadyBeginFrameTask"); + wait_for_compositor_ready_begin_frame_task_.Reset( + base::Bind(&CompositorController::IssueWaitForCompositorReadyBeginFrame, + weak_ptr_factory_.GetWeakPtr())); + + // We may receive the mainFrameReadyForScreenshots event before this task + // is run. In that case, we cancel it in OnMainFrameReadyForScreenshots to + // avoid another unnecessary BeginFrame. + task_runner_->PostDelayedTask( + FROM_HERE, wait_for_compositor_ready_begin_frame_task_.callback(), + wait_for_compositor_ready_begin_frame_delay_); +} + +void CompositorController::IssueWaitForCompositorReadyBeginFrame() { + TRACE_EVENT0("headless", + "CompositorController::IssueWaitForCompositorReadyBeginFrame"); + // No need for PostBeginFrame, since + // wait_for_compositor_ready_begin_frame_task_ has already been posted. + wait_for_compositor_ready_begin_frame_task_.Cancel(); + BeginFrame(base::Bind( + &CompositorController::WaitForCompositorReadyBeginFrameComplete, + weak_ptr_factory_.GetWeakPtr())); +} + +void CompositorController::WaitForCompositorReadyBeginFrameComplete( + std::unique_ptr<BeginFrameResult>) { + TRACE_EVENT0( + "headless", + "CompositorController::WaitForCompositorReadyBeginFrameComplete"); + DCHECK(compositor_ready_callback_); + + if (main_frame_ready_) { + auto callback = compositor_ready_callback_; + compositor_ready_callback_.Reset(); + callback.Run(); + return; + } + + // Continue posting more BeginFrames with a delay until the main frame + // becomes ready. If needs_begin_frames_ is false, it will eventually turn + // true again once the renderer's compositor has started up. + if (needs_begin_frames_) + PostWaitForCompositorReadyBeginFrameTask(); +} + +void CompositorController::WaitUntilIdle(const base::Closure& idle_callback) { + TRACE_EVENT_INSTANT1("headless", "CompositorController::WaitUntilIdle", + TRACE_EVENT_SCOPE_THREAD, "begin_frame_in_flight", + !!begin_frame_complete_callback_); + DCHECK(!idle_callback_); + + if (!begin_frame_complete_callback_) { + idle_callback.Run(); + return; + } + + idle_callback_ = idle_callback; +} + +void CompositorController::WaitForMainFrameContentUpdate( + const base::Closure& main_frame_content_updated_callback) { + TRACE_EVENT0("headless", + "CompositorController::WaitForMainFrameContentUpdate"); + DCHECK(!begin_frame_complete_callback_); + DCHECK(!main_frame_content_updated_callback_); + main_frame_content_updated_callback_ = main_frame_content_updated_callback; + + // Post BeginFrames until we see a main frame update. + if (needs_begin_frames_) + PostWaitForMainFrameContentUpdateBeginFrame(); +} + +void CompositorController::PostWaitForMainFrameContentUpdateBeginFrame() { + TRACE_EVENT0( + "headless", + "CompositorController::PostWaitForMainFrameContentUpdateBeginFrame"); + DCHECK(main_frame_content_updated_callback_); + PostBeginFrame(base::Bind( + &CompositorController::WaitForMainFrameContentUpdateBeginFrameComplete, + weak_ptr_factory_.GetWeakPtr())); +} + +void CompositorController::WaitForMainFrameContentUpdateBeginFrameComplete( + std::unique_ptr<BeginFrameResult> result) { + if (!result) + return; + + TRACE_EVENT1( + "headless", + "CompositorController::WaitForMainFrameContentUpdateBeginFrameComplete", + "main_frame_content_updated", result->GetMainFrameContentUpdated()); + DCHECK(!begin_frame_complete_callback_); + DCHECK(main_frame_content_updated_callback_); + + if (result->GetMainFrameContentUpdated()) { + auto callback = main_frame_content_updated_callback_; + main_frame_content_updated_callback_.Reset(); + callback.Run(); + return; + } + + // Continue posting BeginFrames until we see a main frame update. + if (needs_begin_frames_) + PostWaitForMainFrameContentUpdateBeginFrame(); +} + +void CompositorController::CaptureScreenshot( + ScreenshotParamsFormat format, + int quality, + const base::Callback<void(const std::string&)>& + screenshot_captured_callback) { + TRACE_EVENT0("headless", "CompositorController::CaptureScreenshot"); + DCHECK(!begin_frame_complete_callback_); + DCHECK(!screenshot_captured_callback_); + DCHECK(main_frame_ready_); + + screenshot_captured_callback_ = screenshot_captured_callback; + + // Let AnimationBeginFrameTask know that it doesn't need to issue an + // animation BeginFrame for the current virtual time pause. + animation_task_->CompositorControllerIssuingScreenshotBeginFrame(); + + PostBeginFrame( + base::Bind(&CompositorController::CaptureScreenshotBeginFrameComplete, + weak_ptr_factory_.GetWeakPtr()), + ScreenshotParams::Builder() + .SetFormat(format) + .SetQuality(quality) + .Build()); +} + +void CompositorController::CaptureScreenshotBeginFrameComplete( + std::unique_ptr<BeginFrameResult> result) { + TRACE_EVENT1( + "headless", "CompositorController::CaptureScreenshotBeginFrameComplete", + "hasScreenshotData", + result ? std::to_string(result->HasScreenshotData()) : "invalid"); + DCHECK(screenshot_captured_callback_); + if (result && result->HasScreenshotData()) { + // TODO(eseckler): Look into returning binary screenshot data via DevTools. + std::string decoded_data; + base::Base64Decode(result->GetScreenshotData(), &decoded_data); + auto callback = screenshot_captured_callback_; + screenshot_captured_callback_.Reset(); + callback.Run(decoded_data); + } else { + LOG(ERROR) << "Screenshotting failed, BeginFrameResult has no data and " + "hasDamage is " + << (result ? std::to_string(result->HasScreenshotData()) + : "invalid"); + auto callback = screenshot_captured_callback_; + screenshot_captured_callback_.Reset(); + callback.Run(std::string()); + } +} + +} // namespace headless diff --git a/chromium/headless/public/util/compositor_controller.h b/chromium/headless/public/util/compositor_controller.h new file mode 100644 index 00000000000..09012a4b377 --- /dev/null +++ b/chromium/headless/public/util/compositor_controller.h @@ -0,0 +1,138 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef HEADLESS_PUBLIC_UTIL_COMPOSITOR_CONTROLLER_H_ +#define HEADLESS_PUBLIC_UTIL_COMPOSITOR_CONTROLLER_H_ + +#include "base/callback.h" +#include "base/cancelable_callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/sequenced_task_runner.h" +#include "base/time/time.h" +#include "headless/public/devtools/domains/headless_experimental.h" +#include "headless/public/headless_devtools_client.h" + +namespace headless { + +class VirtualTimeController; + +// Issues BeginFrames (Chromium's vsync signal) while virtual time advances and +// and takes screenshots. +class HEADLESS_EXPORT CompositorController + : public headless_experimental::ExperimentalObserver { + public: + using BeginFrameResult = headless_experimental::BeginFrameResult; + using MainFrameReadyForScreenshotsParams = + headless_experimental::MainFrameReadyForScreenshotsParams; + using NeedsBeginFramesChangedParams = + headless_experimental::NeedsBeginFramesChangedParams; + using ScreenshotParams = headless_experimental::ScreenshotParams; + using ScreenshotParamsFormat = headless_experimental::ScreenshotParamsFormat; + + // |animation_begin_frame_interval| specifies the virtual time between + // individual BeginFrames while virtual time advances. + // |wait_for_compositor_ready_begin_frame_delay| is the real time delay + // between BeginFrames that are sent while waiting for the main frame + // compositor to become ready (real time). + CompositorController( + scoped_refptr<base::SequencedTaskRunner> task_runner, + HeadlessDevToolsClient* devtools_client, + VirtualTimeController* virtual_time_controller, + base::TimeDelta animation_begin_frame_interval, + base::TimeDelta wait_for_compositor_ready_begin_frame_delay); + ~CompositorController() override; + + // Issues BeginFrames until the main frame's compositor has completed + // initialization. Should not be called again until + // |compositor_ready_callback| was run. Should only be called while no other + // BeginFrame is in flight. + void WaitForCompositorReady(const base::Closure& compositor_ready_callback); + + // Executes |idle_callback| when no BeginFrames are in flight. + void WaitUntilIdle(const base::Closure& idle_callback); + + // Issues BeginFrames until a new main frame update was committed. Should not + // be called again until |main_frame_content_updated_callback| was run. Should + // only be called while no other BeginFrame is in flight. + // + // This can be used in situations where e.g. the main frame size changes and + // we need to wait for the update to propagate down into a new surface before + // taking a screenshot. + // TODO(eseckler): Investigate whether we can replace this with surface + // synchronization or some other mechanism that avoids the need for additional + // BeginFrames. + void WaitForMainFrameContentUpdate( + const base::Closure& main_frame_content_updated_callback); + + // Captures a screenshot by issuing a BeginFrame. |quality| is only valid for + // jpeg format screenshots, in range 0..100. Should not be called again until + // |screenshot_captured_callback| was run. Should only be called while no + // other BeginFrame is in flight and after the compositor is ready. + void CaptureScreenshot(ScreenshotParamsFormat format, + int quality, + const base::Callback<void(const std::string&)>& + screenshot_captured_callback); + + private: + class AnimationBeginFrameTask; + + // headless_experimental_::Observer implementation: + void OnNeedsBeginFramesChanged( + const NeedsBeginFramesChangedParams& params) override; + void OnMainFrameReadyForScreenshots( + const MainFrameReadyForScreenshotsParams& params) override; + + // Posts a BeginFrame as a new task to avoid nesting it inside the current + // callstack, which can upset the compositor. + void PostBeginFrame( + const base::Callback<void(std::unique_ptr<BeginFrameResult>)>& + begin_frame_complete_callback, + std::unique_ptr<ScreenshotParams> screenshot = nullptr); + // Issues a BeginFrame synchronously and runs |begin_frame_complete_callback| + // when done. Should not be called again until |begin_frame_complete_callback| + // was run. + void BeginFrame(const base::Callback<void(std::unique_ptr<BeginFrameResult>)>& + begin_frame_complete_callback, + std::unique_ptr<ScreenshotParams> screenshot = nullptr); + // Runs the |begin_frame_complete_callback_| and the |idle_callback_| if set. + void BeginFrameComplete(std::unique_ptr<BeginFrameResult>); + + // Posts a task to issue a BeginFrame while waiting for the + // mainFrameReadyForScreenshots event. The taks may be cancelled by the event. + void PostWaitForCompositorReadyBeginFrameTask(); + void IssueWaitForCompositorReadyBeginFrame(); + void WaitForCompositorReadyBeginFrameComplete( + std::unique_ptr<BeginFrameResult>); + + // Posts a BeginFrame while waiting for a main frame content update. + void PostWaitForMainFrameContentUpdateBeginFrame(); + void WaitForMainFrameContentUpdateBeginFrameComplete( + std::unique_ptr<BeginFrameResult> result); + + void CaptureScreenshotBeginFrameComplete( + std::unique_ptr<BeginFrameResult> result); + + scoped_refptr<base::SequencedTaskRunner> task_runner_; + HeadlessDevToolsClient* devtools_client_; // NOT OWNED + VirtualTimeController* virtual_time_controller_; // NOT OWNED + std::unique_ptr<AnimationBeginFrameTask> animation_task_; + base::Closure compositor_ready_callback_; + base::Closure idle_callback_; + base::Closure main_frame_content_updated_callback_; + base::Callback<void(const std::string&)> screenshot_captured_callback_; + base::Callback<void(std::unique_ptr<BeginFrameResult>)> + begin_frame_complete_callback_; + base::CancelableClosure wait_for_compositor_ready_begin_frame_task_; + base::TimeDelta animation_begin_frame_interval_; + base::TimeDelta wait_for_compositor_ready_begin_frame_delay_; + bool needs_begin_frames_ = false; + bool main_frame_ready_ = false; + base::Time last_begin_frame_time_ = base::Time::UnixEpoch(); + base::WeakPtrFactory<CompositorController> weak_ptr_factory_; +}; + +} // namespace headless + +#endif // HEADLESS_PUBLIC_UTIL_COMPOSITOR_CONTROLLER_H_ diff --git a/chromium/headless/public/util/compositor_controller_browsertest.cc b/chromium/headless/public/util/compositor_controller_browsertest.cc new file mode 100644 index 00000000000..5169490626e --- /dev/null +++ b/chromium/headless/public/util/compositor_controller_browsertest.cc @@ -0,0 +1,110 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "headless/public/util/compositor_controller.h" + +#include "base/bind.h" +#include "base/command_line.h" +#include "build/build_config.h" +#include "cc/base/switches.h" +#include "content/public/common/content_switches.h" +#include "content/public/test/browser_test.h" +#include "headless/public/devtools/domains/runtime.h" +#include "headless/public/headless_browser.h" +#include "headless/public/headless_devtools_client.h" +#include "headless/public/util/virtual_time_controller.h" +#include "headless/test/headless_browser_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace headless { + +// BeginFrameControl is not supported on Mac. +#if !defined(OS_MACOSX) + +namespace { +static constexpr base::TimeDelta kAnimationFrameInterval = + base::TimeDelta::FromMilliseconds(16); +static constexpr base::TimeDelta kWaitForCompositorReadyFrameDelay = + base::TimeDelta::FromMilliseconds(20); +} // namespace + +class CompositorControllerBrowserTest + : public HeadlessAsyncDevTooledBrowserTest { + public: + void SetUpCommandLine(base::CommandLine* command_line) override { + HeadlessAsyncDevTooledBrowserTest::SetUpCommandLine(command_line); + command_line->AppendSwitch(cc::switches::kRunAllCompositorStagesBeforeDraw); + command_line->AppendSwitch(switches::kDisableNewContentRenderingTimeout); + } + + bool GetEnableBeginFrameControl() override { return true; } + + void RunDevTooledTest() override { + virtual_time_controller_ = + base::MakeUnique<VirtualTimeController>(devtools_client_.get()); + compositor_controller_ = base::MakeUnique<CompositorController>( + browser()->BrowserMainThread(), devtools_client_.get(), + virtual_time_controller_.get(), kAnimationFrameInterval, + kWaitForCompositorReadyFrameDelay); + + compositor_controller_->WaitForCompositorReady( + base::Bind(&CompositorControllerBrowserTest::OnCompositorReady, + base::Unretained(this))); + } + + void OnCompositorReady() { + // Request animation frames in the main frame. + devtools_client_->GetRuntime()->Evaluate( + "window.rafCount = 0;" + "function onRaf(timestamp) {" + " window.rafCount++;" + " window.requestAnimationFrame(onRaf);" + "};" + "window.requestAnimationFrame(onRaf);", + base::Bind(&CompositorControllerBrowserTest::OnRafReady, + base::Unretained(this))); + } + + void OnRafReady(std::unique_ptr<runtime::EvaluateResult> result) { + EXPECT_NE(nullptr, result); + EXPECT_FALSE(result->HasExceptionDetails()); + + virtual_time_controller_->GrantVirtualTimeBudget( + emulation::VirtualTimePolicy::ADVANCE, + kNumFrames * kAnimationFrameInterval, base::Bind([]() {}), + base::Bind(&CompositorControllerBrowserTest::OnVirtualTimeBudgetExpired, + base::Unretained(this))); + } + + void OnVirtualTimeBudgetExpired() { + // Get animation frame count. + devtools_client_->GetRuntime()->Evaluate( + "window.rafCount", + base::Bind(&CompositorControllerBrowserTest::OnGetRafCount, + base::Unretained(this))); + } + + void OnGetRafCount(std::unique_ptr<runtime::EvaluateResult> result) { + EXPECT_NE(nullptr, result); + EXPECT_FALSE(result->HasExceptionDetails()); + + EXPECT_EQ(kNumFrames, result->GetResult()->GetValue()->GetInt()); + FinishAsynchronousTest(); + } + + private: + static constexpr int kNumFrames = 3; + + std::unique_ptr<VirtualTimeController> virtual_time_controller_; + std::unique_ptr<CompositorController> compositor_controller_; +}; + +/* static */ +constexpr int CompositorControllerBrowserTest::kNumFrames; + +HEADLESS_ASYNC_DEVTOOLED_TEST_F(CompositorControllerBrowserTest); + +#endif // !defined(OS_MACOSX) + +} // namespace headless diff --git a/chromium/headless/public/util/compositor_controller_unittest.cc b/chromium/headless/public/util/compositor_controller_unittest.cc new file mode 100644 index 00000000000..33aa032de0c --- /dev/null +++ b/chromium/headless/public/util/compositor_controller_unittest.cc @@ -0,0 +1,511 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "headless/public/util/compositor_controller.h" + +#include "base/base64.h" +#include "base/bind.h" +#include "base/json/json_writer.h" +#include "base/memory/ptr_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/test/test_simple_task_runner.h" +#include "headless/public/internal/headless_devtools_client_impl.h" +#include "headless/public/util/testing/mock_devtools_agent_host.h" +#include "headless/public/util/virtual_time_controller.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace headless { + +namespace { +static constexpr base::TimeDelta kAnimationFrameInterval = + base::TimeDelta::FromMilliseconds(16); +static constexpr base::TimeDelta kWaitForCompositorReadyFrameDelay = + base::TimeDelta::FromMilliseconds(20); +} // namespace + +using testing::Return; +using testing::_; + +class TestVirtualTimeController : public VirtualTimeController { + public: + TestVirtualTimeController(HeadlessDevToolsClient* devtools_client) + : VirtualTimeController(devtools_client) {} + ~TestVirtualTimeController() override = default; + + MOCK_METHOD4(GrantVirtualTimeBudget, + void(emulation::VirtualTimePolicy policy, + base::TimeDelta budget, + const base::Callback<void()>& set_up_complete_callback, + const base::Callback<void()>& budget_expired_callback)); + MOCK_METHOD2(ScheduleRepeatingTask, + void(RepeatingTask* task, base::TimeDelta interval)); + MOCK_METHOD1(CancelRepeatingTask, void(RepeatingTask* task)); + + MOCK_CONST_METHOD0(GetVirtualTimeBase, base::Time()); + MOCK_CONST_METHOD0(GetCurrentVirtualTimeOffset, base::TimeDelta()); +}; + +class CompositorControllerTest : public ::testing::Test { + protected: + CompositorControllerTest() { + task_runner_ = base::MakeRefCounted<base::TestSimpleTaskRunner>(); + client_.SetTaskRunnerForTests(task_runner_); + mock_host_ = base::MakeRefCounted<MockDevToolsAgentHost>(); + + EXPECT_CALL(*mock_host_, IsAttached()).WillOnce(Return(false)); + EXPECT_CALL(*mock_host_, AttachClient(&client_)); + client_.AttachToHost(mock_host_.get()); + virtual_time_controller_ = + base::MakeUnique<TestVirtualTimeController>(&client_); + EXPECT_CALL(*virtual_time_controller_, + ScheduleRepeatingTask(_, kAnimationFrameInterval)) + .WillOnce(testing::SaveArg<0>(&task_)); + ExpectHeadlessExperimentalEnable(); + controller_ = base::MakeUnique<CompositorController>( + task_runner_, &client_, virtual_time_controller_.get(), + kAnimationFrameInterval, kWaitForCompositorReadyFrameDelay); + EXPECT_NE(nullptr, task_); + } + + ~CompositorControllerTest() override { + EXPECT_CALL(*virtual_time_controller_, CancelRepeatingTask(_)); + } + + void ExpectHeadlessExperimentalEnable() { + last_command_id_ += 2; + EXPECT_CALL(*mock_host_, + DispatchProtocolMessage( + &client_, + base::StringPrintf( + "{\"id\":%d,\"method\":\"HeadlessExperimental.enable\"," + "\"params\":{}}", + last_command_id_))) + .WillOnce(Return(true)); + } + + void ExpectVirtualTime(double base, double offset) { + auto base_time = base::Time::FromJsTime(base); + auto offset_delta = base::TimeDelta::FromMilliseconds(offset); + EXPECT_CALL(*virtual_time_controller_, GetVirtualTimeBase()) + .WillOnce(Return(base_time)); + EXPECT_CALL(*virtual_time_controller_, GetCurrentVirtualTimeOffset()) + .WillOnce(Return(offset_delta)); + + // Next BeginFrame's time should be the virtual time provided it has + // progressed. + base::Time virtual_time = base_time + offset_delta; + if (virtual_time > next_begin_frame_time_) + next_begin_frame_time_ = virtual_time; + } + + void ExpectBeginFrame(std::unique_ptr<headless_experimental::ScreenshotParams> + screenshot_params = nullptr) { + last_command_id_ += 2; + base::DictionaryValue params; + auto builder = + std::move(headless_experimental::BeginFrameParams::Builder() + .SetFrameTime(next_begin_frame_time_.ToJsTime()) + .SetInterval(kAnimationFrameInterval.InMillisecondsF())); + if (screenshot_params) + builder.SetScreenshot(std::move(screenshot_params)); + // Subsequent BeginFrames should have a later timestamp. + next_begin_frame_time_ += base::TimeDelta::FromMicroseconds(1); + + std::string params_json; + auto params_value = builder.Build()->Serialize(); + base::JSONWriter::Write(*params_value, ¶ms_json); + + EXPECT_CALL( + *mock_host_, + DispatchProtocolMessage( + &client_, + base::StringPrintf( + "{\"id\":%d,\"method\":\"HeadlessExperimental.beginFrame\"," + "\"params\":%s}", + last_command_id_, params_json.c_str()))) + .WillOnce(Return(true)); + } + + void SendBeginFrameReply(bool has_damage, + bool main_frame_content_updated, + const std::string& screenshot_data) { + auto result = headless_experimental::BeginFrameResult::Builder() + .SetHasDamage(has_damage) + .SetMainFrameContentUpdated(main_frame_content_updated) + .Build(); + if (screenshot_data.length()) + result->SetScreenshotData(screenshot_data); + std::string result_json; + auto result_value = result->Serialize(); + base::JSONWriter::Write(*result_value, &result_json); + + client_.DispatchProtocolMessage( + mock_host_.get(), + base::StringPrintf("{\"id\":%d,\"result\":%s}", last_command_id_, + result_json.c_str())); + } + + void SendNeedsBeginFramesEvent(bool needs_begin_frames) { + client_.DispatchProtocolMessage( + mock_host_.get(), + base::StringPrintf("{\"method\":\"HeadlessExperimental." + "needsBeginFramesChanged\",\"params\":{" + "\"needsBeginFrames\":%s}}", + needs_begin_frames ? "true" : "false")); + // Events are dispatched asynchronously. + task_runner_->RunPendingTasks(); + } + + void SendMainFrameReadyForScreenshotsEvent() { + client_.DispatchProtocolMessage(mock_host_.get(), + "{\"method\":\"HeadlessExperimental." + "mainFrameReadyForScreenshots\",\"params\":" + "{}}"); + // Events are dispatched asynchronously. + task_runner_->RunPendingTasks(); + } + + scoped_refptr<base::TestSimpleTaskRunner> task_runner_; + scoped_refptr<MockDevToolsAgentHost> mock_host_; + HeadlessDevToolsClientImpl client_; + std::unique_ptr<TestVirtualTimeController> virtual_time_controller_; + std::unique_ptr<CompositorController> controller_; + int last_command_id_ = -2; + TestVirtualTimeController::RepeatingTask* task_ = nullptr; + base::Time next_begin_frame_time_ = + base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(1); +}; + +TEST_F(CompositorControllerTest, WaitForCompositorReady) { + // Shouldn't send any commands yet as no needsBeginFrames event was sent yet. + bool ready = false; + controller_->WaitForCompositorReady( + base::Bind([](bool* ready) { *ready = true; }, &ready)); + EXPECT_FALSE(ready); + + // Sends BeginFrames with delay while they are needed. + SendNeedsBeginFramesEvent(true); + EXPECT_TRUE(task_runner_->HasPendingTask()); + ExpectVirtualTime(0, 0); + ExpectBeginFrame(); + task_runner_->RunPendingTasks(); + + SendBeginFrameReply(true, false, std::string()); + + EXPECT_TRUE(task_runner_->HasPendingTask()); + EXPECT_EQ(kWaitForCompositorReadyFrameDelay, + task_runner_->NextPendingTaskDelay()); + ExpectVirtualTime(0, 0); + ExpectBeginFrame(); + task_runner_->RunPendingTasks(); + + EXPECT_FALSE(task_runner_->HasPendingTask()); + + SendBeginFrameReply(false, false, std::string()); + + EXPECT_TRUE(task_runner_->HasPendingTask()); + EXPECT_EQ(kWaitForCompositorReadyFrameDelay, + task_runner_->NextPendingTaskDelay()); + ExpectVirtualTime(0, 0); + ExpectBeginFrame(); + task_runner_->RunPendingTasks(); + + // No new BeginFrames are scheduled when BeginFrames are not needed. + SendNeedsBeginFramesEvent(false); + EXPECT_FALSE(task_runner_->HasPendingTask()); + + SendBeginFrameReply(false, false, std::string()); + EXPECT_FALSE(task_runner_->HasPendingTask()); + + // Restarts sending BeginFrames when they are needed again. + SendNeedsBeginFramesEvent(true); + EXPECT_TRUE(task_runner_->HasPendingTask()); + ExpectVirtualTime(0, 0); + ExpectBeginFrame(); + task_runner_->RunPendingTasks(); + + // Stops sending BeginFrames when main frame becomes ready. + SendMainFrameReadyForScreenshotsEvent(); + EXPECT_FALSE(task_runner_->HasPendingTask()); + + EXPECT_FALSE(ready); + SendBeginFrameReply(true, true, std::string()); + EXPECT_FALSE(task_runner_->HasPendingTask()); + EXPECT_TRUE(ready); +} + +TEST_F(CompositorControllerTest, CaptureScreenshot) { + SendMainFrameReadyForScreenshotsEvent(); + EXPECT_FALSE(task_runner_->HasPendingTask()); + + bool done = false; + controller_->CaptureScreenshot( + headless_experimental::ScreenshotParamsFormat::PNG, 100, + base::Bind( + [](bool* done, const std::string& screenshot_data) { + *done = true; + EXPECT_EQ("test", screenshot_data); + }, + &done)); + + EXPECT_TRUE(task_runner_->HasPendingTask()); + ExpectVirtualTime(0, 0); + ExpectBeginFrame( + headless_experimental::ScreenshotParams::Builder() + .SetFormat(headless_experimental::ScreenshotParamsFormat::PNG) + .SetQuality(100) + .Build()); + task_runner_->RunPendingTasks(); + + std::string base64; + base::Base64Encode("test", &base64); + SendBeginFrameReply(true, true, base64); + EXPECT_FALSE(task_runner_->HasPendingTask()); + EXPECT_TRUE(done); +} + +TEST_F(CompositorControllerTest, WaitForMainFrameContentUpdate) { + bool updated = false; + controller_->WaitForMainFrameContentUpdate( + base::Bind([](bool* updated) { *updated = true; }, &updated)); + EXPECT_FALSE(updated); + + // Sends BeginFrames while they are needed. + SendNeedsBeginFramesEvent(true); + EXPECT_TRUE(task_runner_->HasPendingTask()); + ExpectVirtualTime(0, 0); + ExpectBeginFrame(); + task_runner_->RunPendingTasks(); + + SendBeginFrameReply(true, false, std::string()); + + EXPECT_TRUE(task_runner_->HasPendingTask()); + EXPECT_EQ(base::TimeDelta(), task_runner_->NextPendingTaskDelay()); + ExpectVirtualTime(0, 0); + ExpectBeginFrame(); + task_runner_->RunPendingTasks(); + + EXPECT_FALSE(task_runner_->HasPendingTask()); + + SendBeginFrameReply(false, false, std::string()); + + EXPECT_TRUE(task_runner_->HasPendingTask()); + EXPECT_EQ(base::TimeDelta(), task_runner_->NextPendingTaskDelay()); + ExpectVirtualTime(0, 0); + ExpectBeginFrame(); + task_runner_->RunPendingTasks(); + + // No new BeginFrames are scheduled when BeginFrames are not needed. + SendNeedsBeginFramesEvent(false); + EXPECT_FALSE(task_runner_->HasPendingTask()); + + SendBeginFrameReply(false, false, std::string()); + EXPECT_FALSE(task_runner_->HasPendingTask()); + + // Restarts sending BeginFrames when they are needed again. + SendNeedsBeginFramesEvent(true); + EXPECT_TRUE(task_runner_->HasPendingTask()); + ExpectVirtualTime(0, 0); + ExpectBeginFrame(); + task_runner_->RunPendingTasks(); + + // Stops sending BeginFrames when an main frame update is included. + SendMainFrameReadyForScreenshotsEvent(); + EXPECT_FALSE(task_runner_->HasPendingTask()); + + EXPECT_FALSE(updated); + SendBeginFrameReply(true, true, std::string()); + EXPECT_FALSE(task_runner_->HasPendingTask()); + EXPECT_TRUE(updated); +} + +TEST_F(CompositorControllerTest, SendsAnimationFrames) { + bool can_continue = false; + auto continue_callback = base::Bind( + [](bool* can_continue) { *can_continue = true; }, &can_continue); + + // Task doesn't block virtual time request. + task_->BudgetRequested(base::TimeDelta(), + base::TimeDelta::FromMilliseconds(100), + continue_callback); + EXPECT_TRUE(can_continue); + can_continue = false; + + // Doesn't send BeginFrames before virtual time started. + SendNeedsBeginFramesEvent(true); + EXPECT_FALSE(task_runner_->HasPendingTask()); + + // Sends a BeginFrame after interval elapsed. + task_->IntervalElapsed(kAnimationFrameInterval, continue_callback); + EXPECT_FALSE(can_continue); + + EXPECT_TRUE(task_runner_->HasPendingTask()); + ExpectVirtualTime(1000, kAnimationFrameInterval.InMillisecondsF()); + ExpectBeginFrame(); + task_runner_->RunPendingTasks(); + + // Lets virtual time continue after BeginFrame was completed. + SendBeginFrameReply(false, false, std::string()); + EXPECT_FALSE(task_runner_->HasPendingTask()); + EXPECT_TRUE(can_continue); + can_continue = false; + + // Sends another BeginFrame after next interval elapsed. + task_->IntervalElapsed(kAnimationFrameInterval, continue_callback); + EXPECT_FALSE(can_continue); + + EXPECT_TRUE(task_runner_->HasPendingTask()); + ExpectVirtualTime(1000, kAnimationFrameInterval.InMillisecondsF() * 2); + ExpectBeginFrame(); + task_runner_->RunPendingTasks(); + + // Lets virtual time continue after BeginFrame was completed. + SendBeginFrameReply(false, false, std::string()); + EXPECT_FALSE(task_runner_->HasPendingTask()); + EXPECT_TRUE(can_continue); + can_continue = false; +} + +TEST_F(CompositorControllerTest, SkipsAnimationFrameForScreenshots) { + bool can_continue = false; + auto continue_callback = base::Bind( + [](bool* can_continue) { *can_continue = true; }, &can_continue); + + SendMainFrameReadyForScreenshotsEvent(); + EXPECT_FALSE(task_runner_->HasPendingTask()); + SendNeedsBeginFramesEvent(true); + EXPECT_FALSE(task_runner_->HasPendingTask()); + + // Doesn't send a BeginFrame after interval elapsed if a screenshot is taken + // instead. + task_->IntervalElapsed(kAnimationFrameInterval, continue_callback); + EXPECT_FALSE(can_continue); + + controller_->CaptureScreenshot( + headless_experimental::ScreenshotParamsFormat::PNG, 100, + base::Bind([](const std::string&) {})); + + EXPECT_TRUE(task_runner_->HasPendingTask()); + ExpectVirtualTime(0, 0); + ExpectBeginFrame( + headless_experimental::ScreenshotParams::Builder() + .SetFormat(headless_experimental::ScreenshotParamsFormat::PNG) + .SetQuality(100) + .Build()); + task_runner_->RunPendingTasks(); + + EXPECT_TRUE(can_continue); + can_continue = false; +} + +TEST_F(CompositorControllerTest, + PostponesAnimationFrameWhenBudgetExpired) { + bool can_continue = false; + auto continue_callback = base::Bind( + [](bool* can_continue) { *can_continue = true; }, &can_continue); + + SendNeedsBeginFramesEvent(true); + EXPECT_FALSE(task_runner_->HasPendingTask()); + + // Doesn't send a BeginFrame after interval elapsed if the budget also + // expired. + task_->IntervalElapsed(kAnimationFrameInterval, continue_callback); + EXPECT_FALSE(can_continue); + + task_->BudgetExpired(kAnimationFrameInterval); + EXPECT_TRUE(can_continue); + can_continue = false; + // Flush cancelled task. + task_runner_->RunPendingTasks(); + + // Sends a BeginFrame when more virtual time budget is requested. + ExpectVirtualTime(0, 0); + ExpectBeginFrame(); + task_->BudgetRequested(kAnimationFrameInterval, + base::TimeDelta::FromMilliseconds(100), + continue_callback); + EXPECT_FALSE(can_continue); + + SendBeginFrameReply(false, false, std::string()); + EXPECT_FALSE(task_runner_->HasPendingTask()); + EXPECT_TRUE(can_continue); + can_continue = false; +} + +TEST_F(CompositorControllerTest, + SkipsAnimationFrameWhenBudgetExpiredAndScreenshotWasTaken) { + bool can_continue = false; + auto continue_callback = base::Bind( + [](bool* can_continue) { *can_continue = true; }, &can_continue); + + SendMainFrameReadyForScreenshotsEvent(); + EXPECT_FALSE(task_runner_->HasPendingTask()); + SendNeedsBeginFramesEvent(true); + EXPECT_FALSE(task_runner_->HasPendingTask()); + + // Doesn't send a BeginFrame after interval elapsed if the budget also + // expired. + task_->IntervalElapsed(kAnimationFrameInterval, continue_callback); + EXPECT_FALSE(can_continue); + + task_->BudgetExpired(kAnimationFrameInterval); + EXPECT_TRUE(can_continue); + can_continue = false; + // Flush cancelled task. + task_runner_->RunPendingTasks(); + + controller_->CaptureScreenshot( + headless_experimental::ScreenshotParamsFormat::PNG, 100, + base::Bind([](const std::string&) {})); + + EXPECT_TRUE(task_runner_->HasPendingTask()); + ExpectVirtualTime(0, 0); + ExpectBeginFrame( + headless_experimental::ScreenshotParams::Builder() + .SetFormat(headless_experimental::ScreenshotParamsFormat::PNG) + .SetQuality(100) + .Build()); + task_runner_->RunPendingTasks(); + + // Sends a BeginFrame when more virtual time budget is requested. + task_->BudgetRequested(kAnimationFrameInterval, + base::TimeDelta::FromMilliseconds(100), + continue_callback); + EXPECT_TRUE(can_continue); + can_continue = false; +} + +TEST_F(CompositorControllerTest, WaitUntilIdle) { + bool idle = false; + auto idle_callback = base::Bind([](bool* idle) { *idle = true; }, &idle); + + SendNeedsBeginFramesEvent(true); + EXPECT_FALSE(task_runner_->HasPendingTask()); + + // WaitUntilIdle executes callback immediately if no BeginFrame is active. + controller_->WaitUntilIdle(idle_callback); + EXPECT_TRUE(idle); + idle = false; + + // Send a BeginFrame. + task_->IntervalElapsed(kAnimationFrameInterval, base::Bind([]() {})); + + EXPECT_TRUE(task_runner_->HasPendingTask()); + ExpectVirtualTime(0, 0); + ExpectBeginFrame(); + task_runner_->RunPendingTasks(); + + // WaitUntilIdle only executes callback after BeginFrame was completed. + controller_->WaitUntilIdle(idle_callback); + EXPECT_FALSE(idle); + + SendBeginFrameReply(false, false, std::string()); + EXPECT_FALSE(task_runner_->HasPendingTask()); + EXPECT_TRUE(idle); + idle = false; +} + +} // namespace headless diff --git a/chromium/headless/public/util/deterministic_dispatcher.cc b/chromium/headless/public/util/deterministic_dispatcher.cc index bfa75e5b714..e9507c53722 100644 --- a/chromium/headless/public/util/deterministic_dispatcher.cc +++ b/chromium/headless/public/util/deterministic_dispatcher.cc @@ -20,7 +20,7 @@ DeterministicDispatcher::DeterministicDispatcher( navigation_in_progress_(false), weak_ptr_factory_(this) {} -DeterministicDispatcher::~DeterministicDispatcher() {} +DeterministicDispatcher::~DeterministicDispatcher() = default; void DeterministicDispatcher::JobCreated(ManagedDispatchURLRequestJob* job) { base::AutoLock lock(lock_); @@ -144,7 +144,7 @@ void DeterministicDispatcher::NavigationDoneTask() { DeterministicDispatcher::Request::Request() = default; DeterministicDispatcher::Request::Request(Request&&) = default; -DeterministicDispatcher::Request::~Request() {} +DeterministicDispatcher::Request::~Request() = default; DeterministicDispatcher::Request::Request( ManagedDispatchURLRequestJob* url_request) diff --git a/chromium/headless/public/util/deterministic_dispatcher_test.cc b/chromium/headless/public/util/deterministic_dispatcher_test.cc index 48b30e298c5..5b769800ec8 100644 --- a/chromium/headless/public/util/deterministic_dispatcher_test.cc +++ b/chromium/headless/public/util/deterministic_dispatcher_test.cc @@ -23,8 +23,8 @@ namespace headless { class DeterministicDispatcherTest : public ::testing::Test { protected: - DeterministicDispatcherTest() {} - ~DeterministicDispatcherTest() override {} + DeterministicDispatcherTest() = default; + ~DeterministicDispatcherTest() override = default; void SetUp() override { deterministic_dispatcher_.reset( @@ -135,7 +135,7 @@ class NavigationRequestForTest : public NavigationRequest { explicit NavigationRequestForTest(base::Closure* done_closure) : done_closure_(done_closure) {} - ~NavigationRequestForTest() override {} + ~NavigationRequestForTest() override = default; // NavigationRequest implementation: void StartProcessing(base::Closure done_callback) override { diff --git a/chromium/headless/public/util/deterministic_http_protocol_handler.cc b/chromium/headless/public/util/deterministic_http_protocol_handler.cc index 8ff8e37df81..84cc84875be 100644 --- a/chromium/headless/public/util/deterministic_http_protocol_handler.cc +++ b/chromium/headless/public/util/deterministic_http_protocol_handler.cc @@ -19,8 +19,8 @@ namespace headless { class DeterministicHttpProtocolHandler::NopGenericURLRequestJobDelegate : public GenericURLRequestJob::Delegate { public: - NopGenericURLRequestJobDelegate() {} - ~NopGenericURLRequestJobDelegate() override {} + NopGenericURLRequestJobDelegate() = default; + ~NopGenericURLRequestJobDelegate() override = default; void OnResourceLoadFailed(const Request* request, net::Error error) override { } diff --git a/chromium/headless/public/util/error_reporter.cc b/chromium/headless/public/util/error_reporter.cc index 9daa0198670..7f4df916177 100644 --- a/chromium/headless/public/util/error_reporter.cc +++ b/chromium/headless/public/util/error_reporter.cc @@ -7,12 +7,13 @@ #include <sstream> #include "base/logging.h" +#include "base/strings/string_util.h" namespace headless { -ErrorReporter::ErrorReporter() {} +ErrorReporter::ErrorReporter() = default; -ErrorReporter::~ErrorReporter() {} +ErrorReporter::~ErrorReporter() = default; #if DCHECK_IS_ON() void ErrorReporter::Push() { @@ -48,6 +49,10 @@ void ErrorReporter::AddError(base::StringPiece description) { bool ErrorReporter::HasErrors() const { return !errors_.empty(); } + +std::string ErrorReporter::ToString() const { + return base::JoinString(errors_, ", "); +} #endif // DCHECK_IS_ON() } // namespace headless diff --git a/chromium/headless/public/util/error_reporter.h b/chromium/headless/public/util/error_reporter.h index 2fd432f3f2d..c38c60c8940 100644 --- a/chromium/headless/public/util/error_reporter.h +++ b/chromium/headless/public/util/error_reporter.h @@ -39,13 +39,17 @@ class HEADLESS_EXPORT ErrorReporter { // Returns a list of reported errors. const std::vector<std::string>& errors() const { return errors_; } -#else // DCHECK_IS_ON() - inline void Push() {} - inline void Pop() {} - inline void SetName(const char* name) {} - inline void AddError(base::StringPiece description) {} - inline bool HasErrors() const { return false; } - std::vector<std::string> errors() const { return std::vector<std::string>(); } + + // Returns a string containing all the errors concatenated together. + std::string ToString() const; +#else // DCHECK_IS_ON() + void Push() {} + void Pop() {} + void SetName(const char* name) {} + void AddError(base::StringPiece description) {} + bool HasErrors() const { return false; } + std::vector<std::string> errors() const { return {}; } + std::string ToString() const { return ""; } #endif // DCHECK_IS_ON() private: diff --git a/chromium/headless/public/util/expedited_dispatcher.cc b/chromium/headless/public/util/expedited_dispatcher.cc index 649321a7ad0..1fe445cca88 100644 --- a/chromium/headless/public/util/expedited_dispatcher.cc +++ b/chromium/headless/public/util/expedited_dispatcher.cc @@ -16,7 +16,7 @@ ExpeditedDispatcher::ExpeditedDispatcher( scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner) : io_thread_task_runner_(std::move(io_thread_task_runner)) {} -ExpeditedDispatcher::~ExpeditedDispatcher() {} +ExpeditedDispatcher::~ExpeditedDispatcher() = default; void ExpeditedDispatcher::JobCreated(ManagedDispatchURLRequestJob*) {} diff --git a/chromium/headless/public/util/expedited_dispatcher_test.cc b/chromium/headless/public/util/expedited_dispatcher_test.cc index dacc82deab0..05571f6df02 100644 --- a/chromium/headless/public/util/expedited_dispatcher_test.cc +++ b/chromium/headless/public/util/expedited_dispatcher_test.cc @@ -23,8 +23,8 @@ namespace headless { class ExpeditedDispatcherTest : public ::testing::Test { protected: - ExpeditedDispatcherTest() {} - ~ExpeditedDispatcherTest() override {} + ExpeditedDispatcherTest() = default; + ~ExpeditedDispatcherTest() override = default; void SetUp() override { expedited_dispatcher_.reset(new ExpeditedDispatcher(loop_.task_runner())); @@ -92,7 +92,7 @@ class NavigationRequestForTest : public NavigationRequest { explicit NavigationRequestForTest(base::Closure* done_closure) : done_closure_(done_closure) {} - ~NavigationRequestForTest() override {} + ~NavigationRequestForTest() override = default; // NavigationRequest implementation: void StartProcessing(base::Closure done_callback) override { diff --git a/chromium/headless/public/util/generic_url_request_job.cc b/chromium/headless/public/util/generic_url_request_job.cc index ff2dfe86d38..e865fae2f50 100644 --- a/chromium/headless/public/util/generic_url_request_job.cc +++ b/chromium/headless/public/util/generic_url_request_job.cc @@ -8,7 +8,6 @@ #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" @@ -21,6 +20,7 @@ #include "net/base/net_errors.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "net/base/upload_bytes_element_reader.h" +#include "net/cookies/canonical_cookie.h" #include "net/cookies/cookie_store.h" #include "net/http/http_response_headers.h" #include "net/http/http_util.h" @@ -78,7 +78,7 @@ void GenericURLRequestJob::SetExtraRequestHeaders( void GenericURLRequestJob::Start() { PrepareCookies(request_->url(), request_->method(), - url::Origin(request_->site_for_cookies())); + url::Origin::Create(request_->site_for_cookies())); } void GenericURLRequestJob::PrepareCookies(const GURL& rewritten_url, @@ -90,7 +90,7 @@ void GenericURLRequestJob::PrepareCookies(const GURL& rewritten_url, options.set_include_httponly(); // See net::URLRequestHttpJob::AddCookieHeaderAndStart(). - url::Origin requested_origin(rewritten_url); + url::Origin requested_origin = url::Origin::Create(rewritten_url); if (net::registry_controlled_domains::SameDomainOrHost( requested_origin, site_for_cookies, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) { @@ -118,7 +118,7 @@ void GenericURLRequestJob::OnCookiesAvailable( DCHECK(origin_task_runner_->RunsTasksInCurrentSequence()); // TODO(alexclarke): Set user agent. // Pass cookies, the referrer and any extra headers into the fetch request. - std::string cookie = net::CookieStore::BuildCookieLine(cookie_list); + std::string cookie = net::CanonicalCookie::BuildCookieLine(cookie_list); if (!cookie.empty()) extra_request_headers_.SetHeader(net::HttpRequestHeaders::kCookie, cookie); @@ -299,99 +299,133 @@ bool GenericURLRequestJob::IsAsync() const { return true; } +bool GenericURLRequestJob::IsBrowserSideFetch() const { + if (!request_resource_info_) + return false; + return request_resource_info_->GetFrameTreeNodeId() != -1; +} + namespace { void CompletionCallback(int* dest, base::Closure* quit_closure, int value) { *dest = value; quit_closure->Run(); } + +bool ReadAndAppendBytes(const std::unique_ptr<net::UploadElementReader>& reader, + std::string& post_data) { + // TODO(alexclarke): Consider changing the interface of + // GenericURLRequestJob::GetPostData to use a callback which would let us + // avoid the nested run loops below. + + // Read the POST bytes. + uint64_t size_to_stop_at = post_data.size() + reader->GetContentLength(); + const size_t block_size = 1024; + scoped_refptr<net::IOBuffer> read_buffer(new net::IOBuffer(block_size)); + while (post_data.size() < size_to_stop_at) { + 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::RunLoop nested_run_loop(base::RunLoop::Type::kNestableTasksAllowed); + quit_closure = nested_run_loop.QuitClosure(); + nested_run_loop.Run(); + } + + // Bail out if an error occured. + if (bytes_read < 0) + return false; + + post_data.append(read_buffer->data(), bytes_read); + } + return true; +} } // namespace -std::string GenericURLRequestJob::GetPostData() const { +const std::vector<std::unique_ptr<net::UploadElementReader>>* +GenericURLRequestJob::GetInitializedReaders() const { if (!request_->has_upload()) - return ""; + return nullptr; const net::UploadDataStream* stream = request_->get_upload(); if (!stream->GetElementReaders()) - return ""; + return nullptr; if (stream->GetElementReaders()->size() == 0) - return ""; + return nullptr; - DCHECK_EQ(1u, stream->GetElementReaders()->size()); - 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()); + const std::vector<std::unique_ptr<net::UploadElementReader>>* readers = + stream->GetElementReaders(); - // 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 readers, this necessary because some of them will segfault + // if you try to call any method without initializing first. + for (size_t i = 0; i < readers->size(); ++i) { + const std::unique_ptr<net::UploadElementReader>& reader = (*readers)[i]; + if (!reader) + continue; - // 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()); + base::RunLoop nested_run_loop(base::RunLoop::Type::kNestableTasksAllowed); quit_closure = nested_run_loop.QuitClosure(); nested_run_loop.Run(); } if (init_result != net::OK) - return ""; + return nullptr; } - // 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)); + return readers; +} - 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(); - } +std::string GenericURLRequestJob::GetPostData() const { + const std::vector<std::unique_ptr<net::UploadElementReader>>* readers = + GetInitializedReaders(); - // Bail out if an error occured. - if (bytes_read < 0) - return ""; + if (!readers) + return ""; - post_data.append(read_buffer->data(), bytes_read); + uint64_t total_content_length = 0; + for (size_t i = 0; i < readers->size(); ++i) { + const std::unique_ptr<net::UploadElementReader>& reader = (*readers)[i]; + total_content_length += reader->GetContentLength(); + } + std::string post_data; + post_data.reserve(total_content_length); + + for (size_t i = 0; i < readers->size(); ++i) { + const std::unique_ptr<net::UploadElementReader>& reader = (*readers)[i]; + // If |reader| is actually an UploadBytesElementReader we can get the data + // directly. + const net::UploadBytesElementReader* bytes_reader = reader->AsBytesReader(); + if (bytes_reader) { + post_data.append(bytes_reader->bytes(), bytes_reader->length()); + } else { + if (!ReadAndAppendBytes(reader, post_data)) + break; // Bail out if something went wrong. + } } return post_data; } uint64_t GenericURLRequestJob::GetPostDataSize() const { - if (!request_->has_upload()) - return 0; - - const net::UploadDataStream* stream = request_->get_upload(); - if (!stream->GetElementReaders()) - return 0; + const std::vector<std::unique_ptr<net::UploadElementReader>>* readers = + GetInitializedReaders(); - if (stream->GetElementReaders()->size() == 0) + if (!readers) return 0; - DCHECK_EQ(1u, stream->GetElementReaders()->size()); - const std::unique_ptr<net::UploadElementReader>& reader = - (*stream->GetElementReaders())[0]; - return reader->GetContentLength(); + uint64_t total_content_length = 0; + for (size_t i = 0; i < readers->size(); ++i) { + const std::unique_ptr<net::UploadElementReader>& reader = (*readers)[i]; + if (reader) + total_content_length += reader->GetContentLength(); + } + return total_content_length; } } // 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 192e7545104..06c90f7296e 100644 --- a/chromium/headless/public/util/generic_url_request_job.h +++ b/chromium/headless/public/util/generic_url_request_job.h @@ -24,6 +24,7 @@ namespace net { class IOBuffer; +class UploadElementReader; } // namespace net namespace content { @@ -56,6 +57,9 @@ class HEADLESS_EXPORT Request { // Returns the size of the POST data, if any, from the net::URLRequest. virtual uint64_t GetPostDataSize() const = 0; + // Returns true if the fetch was issues by the browser. + virtual bool IsBrowserSideFetch() const = 0; + enum class ResourceType { MAIN_FRAME = 0, SUB_FRAME = 1, @@ -159,6 +163,7 @@ class HEADLESS_EXPORT GenericURLRequestJob uint64_t GetPostDataSize() const override; ResourceType GetResourceType() const override; bool IsAsync() const override; + bool IsBrowserSideFetch() const override; private: void PrepareCookies(const GURL& rewritten_url, @@ -168,6 +173,9 @@ class HEADLESS_EXPORT GenericURLRequestJob const std::string& method, const net::CookieList& cookie_list); + const std::vector<std::unique_ptr<net::UploadElementReader>>* + GetInitializedReaders() const; + std::unique_ptr<URLFetcher> url_fetcher_; net::HttpRequestHeaders extra_request_headers_; scoped_refptr<net::HttpResponseHeaders> response_headers_; 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 e278720295b..777837d4b03 100644 --- a/chromium/headless/public/util/generic_url_request_job_test.cc +++ b/chromium/headless/public/util/generic_url_request_job_test.cc @@ -28,6 +28,7 @@ #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +using testing::NotNull; using testing::_; std::ostream& operator<<(std::ostream& os, const base::DictionaryValue& value) { @@ -57,13 +58,15 @@ class MockDelegate : public MockGenericURLRequestJobDelegate { class MockFetcher : public URLFetcher { public: MockFetcher(base::DictionaryValue* fetch_request, + std::string* received_post_data, 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), + received_post_data_(received_post_data), on_request_callback_(on_request_callback) {} - ~MockFetcher() override {} + ~MockFetcher() override = default; void StartFetch(const Request* request, ResultListener* result_listener) override { @@ -80,9 +83,9 @@ class MockFetcher : public URLFetcher { 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", std::move(post_data)); + *received_post_data_ = request->GetPostData(); + if (!received_post_data_->empty() && received_post_data_->size() < 1024) + fetch_request_->SetString("post_data", *received_post_data_); const auto find_it = json_fetch_reply_map_->find(url); if (find_it == json_fetch_reply_map_->end()) { @@ -97,9 +100,6 @@ class MockFetcher : public URLFetcher { base::DictionaryValue* reply_dictionary; ASSERT_TRUE(fetch_reply->GetAsDictionary(&reply_dictionary)); - std::string final_url; - ASSERT_TRUE(reply_dictionary->GetString("url", &final_url)); - ASSERT_TRUE(reply_dictionary->GetString("data", &response_data_)); base::DictionaryValue* reply_headers_dictionary; ASSERT_TRUE( reply_dictionary->GetDictionary("headers", &reply_headers_dictionary)); @@ -107,10 +107,8 @@ class MockFetcher : public URLFetcher { new net::HttpResponseHeaders("")); for (base::DictionaryValue::Iterator it(*reply_headers_dictionary); !it.IsAtEnd(); it.Advance()) { - std::string value; - ASSERT_TRUE(it.value().GetAsString(&value)); - response_headers->AddHeader( - base::StringPrintf("%s: %s", it.key().c_str(), value.c_str())); + response_headers->AddHeader(base::StringPrintf( + "%s: %s", it.key().c_str(), it.value().GetString().c_str())); } // Set the fields needed for tracing, so that we can check @@ -118,14 +116,20 @@ class MockFetcher : public URLFetcher { net::LoadTimingInfo load_timing_info; load_timing_info.send_start = base::TimeTicks::Max(); load_timing_info.receive_headers_end = base::TimeTicks::Max(); + const base::Value* final_url_value = reply_dictionary->FindKey("url"); + ASSERT_THAT(final_url_value, NotNull()); + const base::Value* response_data_value = reply_dictionary->FindKey("data"); + ASSERT_THAT(response_data_value, NotNull()); + response_data_ = response_data_value->GetString(); result_listener->OnFetchComplete( - GURL(final_url), std::move(response_headers), response_data_.c_str(), - response_data_.size(), load_timing_info); + GURL(final_url_value->GetString()), std::move(response_headers), + response_data_.c_str(), 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 + std::map<std::string, std::string>* json_fetch_reply_map_; // NOT OWNED + base::DictionaryValue* fetch_request_; // NOT OWNED + std::string* received_post_data_; // NOT OWNED base::Callback<void(const Request*)>* on_request_callback_; // NOT OWNED std::string response_data_; // Here to ensure the required lifetime. }; @@ -135,11 +139,13 @@ class MockProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { // Details of the fetch will be stored in |fetch_request|. // The fetch response will be created from parsing |json_fetch_reply_map|. MockProtocolHandler(base::DictionaryValue* fetch_request, + std::string* received_post_data, std::map<std::string, std::string>* json_fetch_reply_map, URLRequestDispatcher* dispatcher, GenericURLRequestJob::Delegate* job_delegate, base::Callback<void(const Request*)>* on_request_callback) : fetch_request_(fetch_request), + received_post_data_(received_post_data), json_fetch_reply_map_(json_fetch_reply_map), job_delegate_(job_delegate), dispatcher_(dispatcher), @@ -151,13 +157,15 @@ 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_, received_post_data_, + json_fetch_reply_map_, on_request_callback_), job_delegate_, nullptr); } private: base::DictionaryValue* fetch_request_; // NOT OWNED + std::string* received_post_data_; // NOT OWNED std::map<std::string, std::string>* json_fetch_reply_map_; // NOT OWNED GenericURLRequestJob::Delegate* job_delegate_; // NOT OWNED URLRequestDispatcher* dispatcher_; // NOT OWNED @@ -170,9 +178,10 @@ class GenericURLRequestJobTest : public testing::Test { public: GenericURLRequestJobTest() : dispatcher_(message_loop_.task_runner()) { url_request_job_factory_.SetProtocolHandler( - "https", base::WrapUnique(new MockProtocolHandler( - &fetch_request_, &json_fetch_reply_map_, &dispatcher_, - &job_delegate_, &on_request_callback_))); + "https", + base::WrapUnique(new MockProtocolHandler( + &fetch_request_, &received_post_data_, &json_fetch_reply_map_, + &dispatcher_, &job_delegate_, &on_request_callback_))); url_request_context_.set_job_factory(&url_request_job_factory_); url_request_context_.set_cookie_store(&cookie_store_); } @@ -219,6 +228,7 @@ class GenericURLRequestJobTest : public testing::Test { MockURLRequestDelegate request_delegate_; base::DictionaryValue fetch_request_; // The request sent to MockFetcher. + std::string received_post_data_; // The POST data (useful if large). std::map<std::string, std::string> json_fetch_reply_map_; // Replies to be sent by MockFetcher. MockDelegate job_delegate_; @@ -304,6 +314,44 @@ TEST_F(GenericURLRequestJobTest, BasicPostRequestParams) { EXPECT_THAT(fetch_request_, MatchesJson(expected_request_json)); } +TEST_F(GenericURLRequestJobTest, LargePostData) { + json_fetch_reply_map_["https://example.com/"] = R"( + { + "url": "https://example.com", + "data": "Reply", + "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->SetReferrer("https://referrer.example.com"); + request->SetExtraRequestHeaderByName("Extra-Header", "Value", true); + request->SetExtraRequestHeaderByName("User-Agent", "TestBrowser", true); + request->SetExtraRequestHeaderByName("Accept", "text/plain", true); + request->set_method("POST"); + + std::vector<char> post_data(4000000); + for (size_t i = 0; i < post_data.size(); i++) + post_data[i] = i & 127; + + request->set_upload(net::ElementsUploadDataStream::CreateWithReader( + base::MakeUnique<net::UploadBytesElementReader>(&post_data[0], + post_data.size()), + 0)); + request->Start(); + base::RunLoop().RunUntilIdle(); + + // Make sure we captured the expected post. + for (size_t i = 0; i < received_post_data_.size(); i++) { + EXPECT_EQ(static_cast<char>(i & 127), post_data[i]); + } + + EXPECT_EQ(post_data.size(), received_post_data_.size()); +} + TEST_F(GenericURLRequestJobTest, BasicRequestProperties) { std::string reply = R"( { @@ -622,4 +670,76 @@ TEST_F(GenericURLRequestJobTest, GetPostDataAsync) { EXPECT_EQ(post_data_size, post_data.size()); } +TEST_F(GenericURLRequestJobTest, LargePostDataNotByteReader) { + json_fetch_reply_map_["https://example.com/"] = R"( + { + "url": "https://example.com", + "data": "Reply", + "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->SetReferrer("https://referrer.example.com"); + request->SetExtraRequestHeaderByName("Extra-Header", "Value", true); + request->SetExtraRequestHeaderByName("User-Agent", "TestBrowser", true); + request->SetExtraRequestHeaderByName("Accept", "text/plain", true); + request->set_method("POST"); + + std::string post_data; + post_data.reserve(4000000); + for (size_t i = 0; i < post_data.size(); i++) + post_data.at(i) = i & 127; + + request->set_upload(net::ElementsUploadDataStream::CreateWithReader( + base::MakeUnique<ByteAtATimeUploadElementReader>(post_data), 0)); + request->Start(); + base::RunLoop().RunUntilIdle(); + + // Make sure we captured the expected post. + for (size_t i = 0; i < received_post_data_.size(); i++) { + EXPECT_EQ(static_cast<char>(i & 127), post_data[i]); + } + + EXPECT_EQ(post_data.size(), received_post_data_.size()); +} + +TEST_F(GenericURLRequestJobTest, PostWithMultipleElements) { + json_fetch_reply_map_["https://example.com/"] = R"( + { + "url": "https://example.com", + "data": "Reply", + "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->SetReferrer("https://referrer.example.com"); + request->SetExtraRequestHeaderByName("Extra-Header", "Value", true); + request->SetExtraRequestHeaderByName("User-Agent", "TestBrowser", true); + request->SetExtraRequestHeaderByName("Accept", "text/plain", true); + request->set_method("POST"); + + std::vector<std::unique_ptr<net::UploadElementReader>> element_readers; + element_readers.push_back( + base::MakeUnique<ByteAtATimeUploadElementReader>("Does ")); + element_readers.push_back( + base::MakeUnique<ByteAtATimeUploadElementReader>("this ")); + element_readers.push_back( + base::MakeUnique<ByteAtATimeUploadElementReader>("work?")); + + request->set_upload(base::MakeUnique<net::ElementsUploadDataStream>( + std::move(element_readers), 0)); + request->Start(); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ("Does this work?", received_post_data_); +} + } // namespace headless diff --git a/chromium/headless/public/util/http_url_fetcher.cc b/chromium/headless/public/util/http_url_fetcher.cc index 20e44ea93de..0e83ebb970f 100644 --- a/chromium/headless/public/util/http_url_fetcher.cc +++ b/chromium/headless/public/util/http_url_fetcher.cc @@ -116,7 +116,7 @@ HttpURLFetcher::Delegate::Delegate( request_->Start(); } -HttpURLFetcher::Delegate::~Delegate() {} +HttpURLFetcher::Delegate::~Delegate() = default; void HttpURLFetcher::Delegate::OnAuthRequired( net::URLRequest* request, @@ -228,7 +228,7 @@ HttpURLFetcher::HttpURLFetcher( const net::URLRequestContext* url_request_context) : url_request_context_(url_request_context) {} -HttpURLFetcher::~HttpURLFetcher() {} +HttpURLFetcher::~HttpURLFetcher() = default; void HttpURLFetcher::StartFetch(const Request* request, ResultListener* result_listener) { diff --git a/chromium/headless/public/util/in_memory_protocol_handler.cc b/chromium/headless/public/util/in_memory_protocol_handler.cc index 27c076551db..49dd8c01657 100644 --- a/chromium/headless/public/util/in_memory_protocol_handler.cc +++ b/chromium/headless/public/util/in_memory_protocol_handler.cc @@ -8,8 +8,8 @@ namespace headless { -InMemoryProtocolHandler::InMemoryProtocolHandler() {} -InMemoryProtocolHandler::~InMemoryProtocolHandler() {} +InMemoryProtocolHandler::InMemoryProtocolHandler() = default; +InMemoryProtocolHandler::~InMemoryProtocolHandler() = default; net::URLRequestJob* InMemoryProtocolHandler::MaybeCreateJob( net::URLRequest* request, diff --git a/chromium/headless/public/util/in_memory_request_job.cc b/chromium/headless/public/util/in_memory_request_job.cc index 5fa84151980..4e9451869c4 100644 --- a/chromium/headless/public/util/in_memory_request_job.cc +++ b/chromium/headless/public/util/in_memory_request_job.cc @@ -21,7 +21,7 @@ InMemoryRequestJob::InMemoryRequestJob( data_offset_(0), weak_factory_(this) {} -InMemoryRequestJob::~InMemoryRequestJob() {} +InMemoryRequestJob::~InMemoryRequestJob() = default; void InMemoryRequestJob::Start() { // Start reading asynchronously so that all error reporting and data diff --git a/chromium/headless/public/util/moveable_auto_lock.h b/chromium/headless/public/util/moveable_auto_lock.h new file mode 100644 index 00000000000..a13cf78b110 --- /dev/null +++ b/chromium/headless/public/util/moveable_auto_lock.h @@ -0,0 +1,64 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef HEADLESS_PUBLIC_UTIL_MOVEABLE_AUTO_LOCK_H_ +#define HEADLESS_PUBLIC_UTIL_MOVEABLE_AUTO_LOCK_H_ + +#include "base/synchronization/lock.h" + +namespace headless { + +class MoveableAutoLock { + public: + explicit MoveableAutoLock(base::Lock& lock) : lock_(lock), moved_(false) { + lock_.Acquire(); + } + + MoveableAutoLock(MoveableAutoLock&& other) + : lock_(other.lock_), moved_(other.moved_) { + lock_.AssertAcquired(); + other.moved_ = true; + } + + ~MoveableAutoLock() { + if (moved_) + return; + lock_.AssertAcquired(); + lock_.Release(); + } + + private: + base::Lock& lock_; + bool moved_; + DISALLOW_COPY_AND_ASSIGN(MoveableAutoLock); +}; + +// RAII helper to allow threadsafe access to an object guarded by a lock. +template <class T> +class LockedPtr { + public: + LockedPtr(MoveableAutoLock&& lock, T* object) + : lock_(std::move(lock)), object_(object) {} + + LockedPtr(LockedPtr&& other) + : lock_(std::move(other.lock_)), object_(other.object_) {} + + T* operator->() { return object_; } + + T& operator*() { return *object_; } + + T* Get() { return object_; } + + explicit operator bool() const { return object_; } + + private: + MoveableAutoLock lock_; + T* object_; + + DISALLOW_COPY_AND_ASSIGN(LockedPtr); +}; + +} // namespace headless + +#endif // HEADLESS_PUBLIC_UTIL_MOVEABLE_AUTO_LOCK_H_ diff --git a/chromium/headless/public/util/testing/generic_url_request_mocks.cc b/chromium/headless/public/util/testing/generic_url_request_mocks.cc index c811651ec15..9c20668d5d9 100644 --- a/chromium/headless/public/util/testing/generic_url_request_mocks.cc +++ b/chromium/headless/public/util/testing/generic_url_request_mocks.cc @@ -20,7 +20,7 @@ namespace headless { MockGenericURLRequestJobDelegate::MockGenericURLRequestJobDelegate() : main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()) {} -MockGenericURLRequestJobDelegate::~MockGenericURLRequestJobDelegate() {} +MockGenericURLRequestJobDelegate::~MockGenericURLRequestJobDelegate() = default; void MockGenericURLRequestJobDelegate::OnResourceLoadFailed( const Request* request, @@ -34,8 +34,8 @@ void MockGenericURLRequestJobDelegate::OnResourceLoadComplete( size_t body_size) {} // MockCookieStore -MockCookieStore::MockCookieStore() {} -MockCookieStore::~MockCookieStore() {} +MockCookieStore::MockCookieStore() = default; +MockCookieStore::~MockCookieStore() = default; void MockCookieStore::SetCookieWithOptionsAsync( const GURL& url, @@ -45,22 +45,6 @@ void MockCookieStore::SetCookieWithOptionsAsync( CHECK(false); } -void MockCookieStore::SetCookieWithDetailsAsync(const GURL& url, - const std::string& name, - const std::string& value, - const std::string& domain, - const std::string& path, - base::Time creation_time, - base::Time expiration_time, - base::Time last_access_time, - bool secure, - bool http_only, - net::CookieSameSite same_site, - net::CookiePriority priority, - SetCookiesCallback callback) { - CHECK(false); -} - void MockCookieStore::SetCanonicalCookieAsync( std::unique_ptr<net::CanonicalCookie> cookie, bool secure_source, @@ -161,10 +145,11 @@ void MockCookieStore::SendCookies(const GURL& url, } // MockURLRequestDelegate -MockURLRequestDelegate::MockURLRequestDelegate() {} -MockURLRequestDelegate::~MockURLRequestDelegate() {} +MockURLRequestDelegate::MockURLRequestDelegate() = default; +MockURLRequestDelegate::~MockURLRequestDelegate() = default; -void MockURLRequestDelegate::OnResponseStarted(net::URLRequest* request) {} +void MockURLRequestDelegate::OnResponseStarted(net::URLRequest* request, + int net_error) {} void MockURLRequestDelegate::OnReadCompleted(net::URLRequest* request, int bytes_read) {} const std::string& MockURLRequestDelegate::response_data() const { 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 36b049114ea..e1ff2ab014f 100644 --- a/chromium/headless/public/util/testing/generic_url_request_mocks.h +++ b/chromium/headless/public/util/testing/generic_url_request_mocks.h @@ -50,20 +50,6 @@ class MockCookieStore : public net::CookieStore { const net::CookieOptions& options, SetCookiesCallback callback) override; - void SetCookieWithDetailsAsync(const GURL& url, - const std::string& name, - const std::string& value, - const std::string& domain, - const std::string& path, - base::Time creation_time, - base::Time expiration_time, - base::Time last_access_time, - bool secure, - bool http_only, - net::CookieSameSite same_site, - net::CookiePriority priority, - SetCookiesCallback callback) override; - void SetCanonicalCookieAsync(std::unique_ptr<net::CanonicalCookie> cookie, bool secure_source, bool modify_http_only, @@ -129,7 +115,7 @@ class MockURLRequestDelegate : public net::URLRequest::Delegate { MockURLRequestDelegate(); ~MockURLRequestDelegate() override; - void OnResponseStarted(net::URLRequest* request) override; + void OnResponseStarted(net::URLRequest* request, int net_error) override; void OnReadCompleted(net::URLRequest* request, int bytes_read) override; const std::string& response_data() const; const net::IOBufferWithSize* metadata() const; diff --git a/chromium/headless/public/util/testing/mock_devtools_agent_host.cc b/chromium/headless/public/util/testing/mock_devtools_agent_host.cc index 78959226a1d..683b4f99d33 100644 --- a/chromium/headless/public/util/testing/mock_devtools_agent_host.cc +++ b/chromium/headless/public/util/testing/mock_devtools_agent_host.cc @@ -6,8 +6,8 @@ namespace headless { -MockDevToolsAgentHost::MockDevToolsAgentHost() {} +MockDevToolsAgentHost::MockDevToolsAgentHost() = default; -MockDevToolsAgentHost::~MockDevToolsAgentHost() {} +MockDevToolsAgentHost::~MockDevToolsAgentHost() = default; } // namespace headless diff --git a/chromium/headless/public/util/testing/test_in_memory_protocol_handler.cc b/chromium/headless/public/util/testing/test_in_memory_protocol_handler.cc index 8864b0b7b44..8883fb1d107 100644 --- a/chromium/headless/public/util/testing/test_in_memory_protocol_handler.cc +++ b/chromium/headless/public/util/testing/test_in_memory_protocol_handler.cc @@ -13,12 +13,9 @@ namespace headless { class TestInMemoryProtocolHandler::MockURLFetcher : public URLFetcher { public: - MockURLFetcher( - TestInMemoryProtocolHandler* protocol_handler, - scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner) - : protocol_handler_(protocol_handler), - io_thread_task_runner_(io_thread_task_runner) {} - ~MockURLFetcher() override {} + explicit MockURLFetcher(TestInMemoryProtocolHandler* protocol_handler) + : protocol_handler_(protocol_handler) {} + ~MockURLFetcher() override = default; // URLFetcher implementation: void StartFetch(const Request* request, @@ -30,12 +27,10 @@ class TestInMemoryProtocolHandler::MockURLFetcher : public URLFetcher { DCHECK_NE(devtools_frame_id, "") << " For url " << url; protocol_handler_->RegisterUrl(url.spec(), devtools_frame_id); - if (protocol_handler_->simulate_slow_fetch()) { - io_thread_task_runner_->PostDelayedTask( - FROM_HERE, - base::Bind(&MockURLFetcher::FinishFetch, base::Unretained(this), - result_listener, url), - base::TimeDelta::FromMilliseconds(100)); + if (protocol_handler_->request_deferrer()) { + protocol_handler_->request_deferrer()->OnRequest( + url, base::Bind(&MockURLFetcher::FinishFetch, base::Unretained(this), + result_listener, url)); } else { FinishFetch(result_listener, url); } @@ -46,6 +41,7 @@ class TestInMemoryProtocolHandler::MockURLFetcher : public URLFetcher { protocol_handler_->GetResponse(url.spec()); if (response) { net::LoadTimingInfo load_timing_info; + load_timing_info.receive_headers_end = base::TimeTicks::Now(); result_listener->OnFetchCompleteExtractHeaders( url, response->data.c_str(), response->data.size(), load_timing_info); } else { @@ -55,7 +51,6 @@ class TestInMemoryProtocolHandler::MockURLFetcher : public URLFetcher { private: TestInMemoryProtocolHandler* protocol_handler_; // NOT OWNED. - scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner_; DISALLOW_COPY_AND_ASSIGN(MockURLFetcher); }; @@ -63,8 +58,8 @@ class TestInMemoryProtocolHandler::MockURLFetcher : public URLFetcher { class TestInMemoryProtocolHandler::TestDelegate : public GenericURLRequestJob::Delegate { public: - TestDelegate() {} - ~TestDelegate() override {} + TestDelegate() = default; + ~TestDelegate() override = default; // GenericURLRequestJob::Delegate implementation: void OnResourceLoadFailed(const Request* request, net::Error error) override { @@ -85,14 +80,14 @@ class TestInMemoryProtocolHandler::TestDelegate TestInMemoryProtocolHandler::TestInMemoryProtocolHandler( scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner, - bool simulate_slow_fetch) + RequestDeferrer* request_deferrer) : test_delegate_(new TestDelegate()), dispatcher_(new ExpeditedDispatcher(io_thread_task_runner)), headless_browser_context_(nullptr), - simulate_slow_fetch_(simulate_slow_fetch), + request_deferrer_(request_deferrer), io_thread_task_runner_(io_thread_task_runner) {} -TestInMemoryProtocolHandler::~TestInMemoryProtocolHandler() {} +TestInMemoryProtocolHandler::~TestInMemoryProtocolHandler() = default; void TestInMemoryProtocolHandler::SetHeadlessBrowserContext( HeadlessBrowserContext* headless_browser_context) { @@ -119,8 +114,7 @@ net::URLRequestJob* TestInMemoryProtocolHandler::MaybeCreateJob( return new GenericURLRequestJob( request, network_delegate, dispatcher_.get(), base::MakeUnique<MockURLFetcher>( - const_cast<TestInMemoryProtocolHandler*>(this), - io_thread_task_runner_), + const_cast<TestInMemoryProtocolHandler*>(this)), test_delegate_.get(), headless_browser_context_); } diff --git a/chromium/headless/public/util/testing/test_in_memory_protocol_handler.h b/chromium/headless/public/util/testing/test_in_memory_protocol_handler.h index c2d6d1f48f0..b3265725097 100644 --- a/chromium/headless/public/util/testing/test_in_memory_protocol_handler.h +++ b/chromium/headless/public/util/testing/test_in_memory_protocol_handler.h @@ -19,9 +19,21 @@ class HeadlessBrowserContext; class TestInMemoryProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { public: + class RequestDeferrer { + public: + virtual ~RequestDeferrer() {} + + // Notifies that the target page has made a request for the |url|. The + // request will not be completed until |complete_request| is run. + // NOTE this will be called on the IO thread. + virtual void OnRequest(const GURL& url, base::Closure complete_request) = 0; + }; + + // Note |request_deferrer| is optional. If the test doesn't need to control + // when resources are fulfilled then pass in nullptr. TestInMemoryProtocolHandler( scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner, - bool simulate_slow_fetch); + RequestDeferrer* request_deferrer); ~TestInMemoryProtocolHandler() override; @@ -30,6 +42,7 @@ class TestInMemoryProtocolHandler struct Response { Response() {} + Response(const std::string& data) : data(data) {} Response(const std::string& body, const std::string& mime_type) : data("HTTP/1.1 200 OK\r\nContent-Type: " + mime_type + "\r\n\r\n" + body) {} @@ -60,7 +73,7 @@ class TestInMemoryProtocolHandler url_to_devtools_frame_id_[url] = devtools_frame_id; } - bool simulate_slow_fetch() const { return simulate_slow_fetch_; } + RequestDeferrer* request_deferrer() const { return request_deferrer_; } class TestDelegate; class MockURLFetcher; @@ -73,7 +86,7 @@ class TestInMemoryProtocolHandler HeadlessBrowserContext* headless_browser_context_; std::map<std::string, std::string> url_to_devtools_frame_id_; std::vector<std::string> urls_requested_; - bool simulate_slow_fetch_; + RequestDeferrer* request_deferrer_; // NOT OWNED. scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner_; DISALLOW_COPY_AND_ASSIGN(TestInMemoryProtocolHandler); diff --git a/chromium/headless/public/util/throttled_dispatcher.cc b/chromium/headless/public/util/throttled_dispatcher.cc index 48efca74255..0e767605568 100644 --- a/chromium/headless/public/util/throttled_dispatcher.cc +++ b/chromium/headless/public/util/throttled_dispatcher.cc @@ -17,7 +17,7 @@ ThrottledDispatcher::ThrottledDispatcher( : ExpeditedDispatcher(std::move(io_thread_task_runner)), requests_paused_(false) {} -ThrottledDispatcher::~ThrottledDispatcher() {} +ThrottledDispatcher::~ThrottledDispatcher() = default; void ThrottledDispatcher::PauseRequests() { base::AutoLock lock(lock_); diff --git a/chromium/headless/public/util/throttled_dispatcher_test.cc b/chromium/headless/public/util/throttled_dispatcher_test.cc index 5e3ba3f382b..d37c9fb1b80 100644 --- a/chromium/headless/public/util/throttled_dispatcher_test.cc +++ b/chromium/headless/public/util/throttled_dispatcher_test.cc @@ -23,8 +23,8 @@ namespace headless { class ThrottledDispatcherTest : public ::testing::Test { protected: - ThrottledDispatcherTest() {} - ~ThrottledDispatcherTest() override {} + ThrottledDispatcherTest() = default; + ~ThrottledDispatcherTest() override = default; void SetUp() override { throttled_dispatcher_.reset(new ThrottledDispatcher(loop_.task_runner())); diff --git a/chromium/headless/public/util/virtual_time_controller.cc b/chromium/headless/public/util/virtual_time_controller.cc index 5505bcb3d8f..3dd107dc628 100644 --- a/chromium/headless/public/util/virtual_time_controller.cc +++ b/chromium/headless/public/util/virtual_time_controller.cc @@ -13,8 +13,10 @@ namespace headless { using base::TimeDelta; VirtualTimeController::VirtualTimeController( - HeadlessDevToolsClient* devtools_client) - : devtools_client_(devtools_client) { + HeadlessDevToolsClient* devtools_client, + int max_task_starvation_count) + : devtools_client_(devtools_client), + max_task_starvation_count_(max_task_starvation_count) { devtools_client_->GetEmulation()->GetExperimental()->AddObserver(this); } @@ -24,7 +26,7 @@ VirtualTimeController::~VirtualTimeController() { void VirtualTimeController::GrantVirtualTimeBudget( emulation::VirtualTimePolicy policy, - int budget_ms, + base::TimeDelta budget, const base::Callback<void()>& set_up_complete_callback, const base::Callback<void()>& budget_expired_callback) { DCHECK(!set_up_complete_callback_); @@ -32,13 +34,19 @@ void VirtualTimeController::GrantVirtualTimeBudget( set_up_complete_callback_ = set_up_complete_callback; budget_expired_callback_ = budget_expired_callback; - requested_budget_ = TimeDelta::FromMilliseconds(budget_ms); - accumulated_time_ = TimeDelta(); + requested_budget_ = budget; + accumulated_budget_portion_ = TimeDelta(); virtual_time_policy_ = policy; // Notify tasks of new budget request. for (TaskEntry& entry : tasks_) - NotifyTaskBudgetRequested(&entry, budget_ms); + entry.ready_to_advance = false; + + iterating_over_tasks_ = true; + for (TaskEntry& entry : tasks_) + NotifyTaskBudgetRequested(&entry, requested_budget_); + iterating_over_tasks_ = false; + DeleteTasksIfRequested(); // If there tasks, NotifyTasksAndAdvance is called from TaskReadyToAdvance. if (tasks_.empty()) @@ -56,22 +64,39 @@ void VirtualTimeController::NotifyTasksAndAdvance() { // Give at most as much virtual time as available until the next callback. bool ready_to_advance = true; - TimeDelta next_budget = requested_budget_ - accumulated_time_; + iterating_over_tasks_ = true; + TimeDelta next_budget = requested_budget_ - accumulated_budget_portion_; + // TODO(alexclarke): This is a little ugly, find a nicer way. + for (TaskEntry& entry : tasks_) { + if (entry.next_execution_time <= total_elapsed_time_offset_) + entry.ready_to_advance = false; + } + for (TaskEntry& entry : tasks_) { - if (entry.next_execution_time <= total_elapsed_time_) + if (entry.next_execution_time <= total_elapsed_time_offset_) NotifyTaskIntervalElapsed(&entry); - ready_to_advance &= entry.ready_to_advance; - next_budget = - std::min(next_budget, entry.next_execution_time - total_elapsed_time_); + if (tasks_to_delete_.find(entry.task) == tasks_to_delete_.end()) { + ready_to_advance &= entry.ready_to_advance; + next_budget = std::min( + next_budget, entry.next_execution_time - total_elapsed_time_offset_); + } } + iterating_over_tasks_ = false; + DeleteTasksIfRequested(); if (!ready_to_advance) return; - if (accumulated_time_ >= requested_budget_) { + if (accumulated_budget_portion_ >= requested_budget_) { + for (TaskEntry& entry : tasks_) + entry.ready_to_advance = false; + + iterating_over_tasks_ = true; for (const TaskEntry& entry : tasks_) - entry.task->BudgetExpired(total_elapsed_time_); + entry.task->BudgetExpired(total_elapsed_time_offset_); + iterating_over_tasks_ = false; + DeleteTasksIfRequested(); auto callback = budget_expired_callback_; budget_expired_callback_.Reset(); @@ -82,21 +107,31 @@ void VirtualTimeController::NotifyTasksAndAdvance() { SetVirtualTimePolicy(next_budget); } +void VirtualTimeController::DeleteTasksIfRequested() { + DCHECK(!iterating_over_tasks_); + + // It's not safe to delete tasks while iterating over |tasks_| so do it now. + while (!tasks_to_delete_.empty()) { + CancelRepeatingTask(*tasks_to_delete_.begin()); + tasks_to_delete_.erase(tasks_to_delete_.begin()); + } +} + void VirtualTimeController::NotifyTaskIntervalElapsed(TaskEntry* entry) { - entry->ready_to_advance = false; - entry->next_execution_time = total_elapsed_time_ + entry->interval; + DCHECK(!entry->ready_to_advance); + entry->next_execution_time = total_elapsed_time_offset_ + entry->interval; entry->task->IntervalElapsed( - total_elapsed_time_, + total_elapsed_time_offset_, base::Bind(&VirtualTimeController::TaskReadyToAdvance, base::Unretained(this), base::Unretained(entry))); } void VirtualTimeController::NotifyTaskBudgetRequested(TaskEntry* entry, - int budget_ms) { - entry->ready_to_advance = false; + base::TimeDelta budget) { + DCHECK(!entry->ready_to_advance); entry->task->BudgetRequested( - total_elapsed_time_, budget_ms, + total_elapsed_time_offset_, budget, base::Bind(&VirtualTimeController::TaskReadyToAdvance, base::Unretained(this), base::Unretained(entry))); } @@ -106,20 +141,26 @@ void VirtualTimeController::TaskReadyToAdvance(TaskEntry* entry) { NotifyTasksAndAdvance(); } -void VirtualTimeController::SetVirtualTimePolicy(const TimeDelta& next_budget) { +void VirtualTimeController::SetVirtualTimePolicy(base::TimeDelta next_budget) { last_used_budget_ = next_budget; virtual_time_active_ = true; devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( emulation::SetVirtualTimePolicyParams::Builder() .SetPolicy(virtual_time_policy_) - .SetBudget(next_budget.InMilliseconds()) + .SetBudget(next_budget.InMillisecondsF()) + .SetMaxVirtualTimeTaskStarvationCount(max_task_starvation_count_) .Build(), base::Bind(&VirtualTimeController::SetVirtualTimePolicyDone, base::Unretained(this))); } void VirtualTimeController::SetVirtualTimePolicyDone( - std::unique_ptr<emulation::SetVirtualTimePolicyResult>) { + std::unique_ptr<emulation::SetVirtualTimePolicyResult> result) { + if (result) { + virtual_time_base_ = base::Time::FromJsTime(result->GetVirtualTimeBase()); + } else { + LOG(WARNING) << "SetVirtualTimePolicy did not succeed"; + } if (set_up_complete_callback_) { set_up_complete_callback_.Run(); set_up_complete_callback_.Reset(); @@ -133,14 +174,14 @@ void VirtualTimeController::OnVirtualTimeBudgetExpired( if (!budget_expired_callback_) return; - accumulated_time_ += last_used_budget_; - total_elapsed_time_ += last_used_budget_; + accumulated_budget_portion_ += last_used_budget_; + total_elapsed_time_offset_ += last_used_budget_; virtual_time_active_ = false; NotifyTasksAndAdvance(); } void VirtualTimeController::ScheduleRepeatingTask(RepeatingTask* task, - int interval_ms) { + base::TimeDelta interval) { if (virtual_time_active_) { // We cannot accurately modify any previously granted virtual time budget. LOG(WARNING) << "VirtualTimeController tasks should be added while " @@ -149,14 +190,26 @@ void VirtualTimeController::ScheduleRepeatingTask(RepeatingTask* task, TaskEntry entry; entry.task = task; - entry.interval = TimeDelta::FromMilliseconds(interval_ms); - entry.next_execution_time = total_elapsed_time_ + entry.interval; + entry.interval = interval; + entry.next_execution_time = total_elapsed_time_offset_ + entry.interval; tasks_.push_back(entry); } void VirtualTimeController::CancelRepeatingTask(RepeatingTask* task) { + if (iterating_over_tasks_) { + tasks_to_delete_.insert(task); + return; + } tasks_.remove_if( [task](const TaskEntry& entry) { return entry.task == task; }); } +base::Time VirtualTimeController::GetVirtualTimeBase() const { + return virtual_time_base_; +} + +base::TimeDelta VirtualTimeController::GetCurrentVirtualTimeOffset() const { + return total_elapsed_time_offset_; +} + } // namespace headless diff --git a/chromium/headless/public/util/virtual_time_controller.h b/chromium/headless/public/util/virtual_time_controller.h index ffeee16f36b..05503aa11eb 100644 --- a/chromium/headless/public/util/virtual_time_controller.h +++ b/chromium/headless/public/util/virtual_time_controller.h @@ -19,11 +19,11 @@ namespace headless { class HEADLESS_EXPORT VirtualTimeController : public emulation::ExperimentalObserver { public: - explicit VirtualTimeController(HeadlessDevToolsClient* devtools_client); + VirtualTimeController(HeadlessDevToolsClient* devtools_client, + int max_task_starvation_count = 0); ~VirtualTimeController() override; - // Grants |budget_ms| milliseconds of virtual time by applying the provided - // |policy|. + // Grants a |budget| of virtual time by applying the provided |policy|. // // |set_up_complete_callback|, if set, is run after the (initial) policy was // applied via DevTools. |budget_expired_callback| will be called when the @@ -31,9 +31,9 @@ class HEADLESS_EXPORT VirtualTimeController // // Should not be called again until the previous invocation's // budget_expired_callback was executed. - void GrantVirtualTimeBudget( + virtual void GrantVirtualTimeBudget( emulation::VirtualTimePolicy policy, - int budget_ms, + base::TimeDelta budget, const base::Callback<void()>& set_up_complete_callback, const base::Callback<void()>& budget_expired_callback); @@ -42,35 +42,48 @@ class HEADLESS_EXPORT VirtualTimeController virtual ~RepeatingTask() {} // Called when the tasks's requested virtual time interval has elapsed. - // |virtual_time| is the virtual time duration that has advanced since the - // page started loading (millisecond granularity). The task should call - // |continue_callback| when it is ready for virtual time to continue + // |virtual_time_offset| is the virtual time duration that has advanced + // since the page started loading (millisecond granularity). The task should + // call |continue_callback| when it is ready for virtual time to continue // advancing. virtual void IntervalElapsed( - const base::TimeDelta& virtual_time, + base::TimeDelta virtual_time_offset, const base::Callback<void()>& continue_callback) = 0; // Called when a new virtual time budget grant was requested. The task // should call |continue_callback| when it is ready for virtual time to // continue advancing. virtual void BudgetRequested( - const base::TimeDelta& virtual_time, - int requested_budget_ms, + base::TimeDelta virtual_time_offset, + base::TimeDelta requested_budget, const base::Callback<void()>& continue_callback) = 0; // Called when the latest virtual time budget has been used up. - virtual void BudgetExpired(const base::TimeDelta& virtual_time) = 0; + virtual void BudgetExpired(base::TimeDelta virtual_time_offset) = 0; }; - // Interleaves execution of the provided |task| with advancing of virtual - // time. The task will be notified whenever another |interval_ms| milliseconds - // of virtual time have elapsed, as well as when the last granted budget has - // been used up. + // Interleaves execution of the provided |task| with progression of virtual + // time. The task will be notified whenever another |interval| of virtual time + // have elapsed, as well as when the last granted budget has been used up. // // To ensure that the task is notified of elapsed intervals accurately, it // should be added while virtual time is paused. - void ScheduleRepeatingTask(RepeatingTask* task, int interval_ms); - void CancelRepeatingTask(RepeatingTask* task); + virtual void ScheduleRepeatingTask(RepeatingTask* task, + base::TimeDelta interval); + virtual void CancelRepeatingTask(RepeatingTask* task); + + // Returns the time that virtual time offsets are relative to. + virtual base::Time GetVirtualTimeBase() const; + + // Returns the current virtual time offset. Only accurate while virtual time + // is paused. + virtual base::TimeDelta GetCurrentVirtualTimeOffset() const; + + // Returns the current virtual time stamp. Only accurate while virtual time + // is paused. + base::Time GetCurrentVirtualTime() const { + return GetVirtualTimeBase() + GetCurrentVirtualTimeOffset(); + } private: struct TaskEntry { @@ -86,14 +99,17 @@ class HEADLESS_EXPORT VirtualTimeController void NotifyTasksAndAdvance(); void NotifyTaskIntervalElapsed(TaskEntry* entry); - void NotifyTaskBudgetRequested(TaskEntry* entry, int budget_ms); + void NotifyTaskBudgetRequested(TaskEntry* entry, base::TimeDelta budget); void TaskReadyToAdvance(TaskEntry* entry); - void SetVirtualTimePolicy(const base::TimeDelta& next_budget); + void DeleteTasksIfRequested(); + + void SetVirtualTimePolicy(base::TimeDelta next_budget); void SetVirtualTimePolicyDone( std::unique_ptr<emulation::SetVirtualTimePolicyResult>); - HeadlessDevToolsClient* devtools_client_; // NOT OWNED + HeadlessDevToolsClient* const devtools_client_; // NOT OWNED + const int max_task_starvation_count_; emulation::VirtualTimePolicy virtual_time_policy_ = emulation::VirtualTimePolicy::ADVANCE; @@ -101,13 +117,17 @@ class HEADLESS_EXPORT VirtualTimeController base::Callback<void()> budget_expired_callback_; bool virtual_time_active_ = false; - base::TimeDelta total_elapsed_time_; + base::TimeDelta total_elapsed_time_offset_; base::TimeDelta requested_budget_; base::TimeDelta last_used_budget_; - base::TimeDelta accumulated_time_; + base::TimeDelta accumulated_budget_portion_; + // Initial virtual time that virtual time offsets are relative to. + base::Time virtual_time_base_; std::list<TaskEntry> tasks_; + std::set<RepeatingTask*> tasks_to_delete_; bool in_notify_tasks_and_advance_ = false; + bool iterating_over_tasks_ = false; }; } // namespace headless diff --git a/chromium/headless/public/util/virtual_time_controller_test.cc b/chromium/headless/public/util/virtual_time_controller_test.cc index 469d6104bb5..483a99bc21a 100644 --- a/chromium/headless/public/util/virtual_time_controller_test.cc +++ b/chromium/headless/public/util/virtual_time_controller_test.cc @@ -16,6 +16,8 @@ namespace headless { +using testing::ElementsAre; +using testing::Mock; using testing::Return; using testing::_; @@ -29,17 +31,18 @@ class VirtualTimeControllerTest : public ::testing::Test { EXPECT_CALL(*mock_host_, IsAttached()).WillOnce(Return(false)); EXPECT_CALL(*mock_host_, AttachClient(&client_)); client_.AttachToHost(mock_host_.get()); - controller_ = base::MakeUnique<VirtualTimeController>(&client_); + controller_ = base::MakeUnique<VirtualTimeController>(&client_, 0); } - ~VirtualTimeControllerTest() override {} + ~VirtualTimeControllerTest() override = default; void GrantVirtualTimeBudget(int budget_ms) { ASSERT_FALSE(set_up_complete_); ASSERT_FALSE(budget_expired_); controller_->GrantVirtualTimeBudget( - emulation::VirtualTimePolicy::ADVANCE, budget_ms, + emulation::VirtualTimePolicy::ADVANCE, + base::TimeDelta::FromMilliseconds(budget_ms), base::Bind( [](bool* set_up_complete) { EXPECT_FALSE(*set_up_complete); @@ -75,16 +78,34 @@ class VirtualTimeControllerTest : public ::testing::Test { }; TEST_F(VirtualTimeControllerTest, AdvancesTimeWithoutTasks) { + controller_ = base::MakeUnique<VirtualTimeController>(&client_, 1000); + + EXPECT_CALL(*mock_host_, + DispatchProtocolMessage( + &client_, + "{\"id\":0,\"method\":\"Emulation.setVirtualTimePolicy\"," + "\"params\":{\"budget\":5000.0," + "\"maxVirtualTimeTaskStarvationCount\":1000," + "\"policy\":\"advance\"}}")) + .WillOnce(Return(true)); + + GrantVirtualTimeBudget(5000); +} + +TEST_F(VirtualTimeControllerTest, MaxVirtualTimeTaskStarvationCount) { EXPECT_CALL(*mock_host_, DispatchProtocolMessage( &client_, "{\"id\":0,\"method\":\"Emulation.setVirtualTimePolicy\"," - "\"params\":{\"budget\":5000,\"policy\":\"advance\"}}")) + "\"params\":{\"budget\":5000.0," + "\"maxVirtualTimeTaskStarvationCount\":0," + "\"policy\":\"advance\"}}")) .WillOnce(Return(true)); GrantVirtualTimeBudget(5000); - client_.DispatchProtocolMessage(mock_host_.get(), "{\"id\":0,\"result\":{}}"); + client_.DispatchProtocolMessage( + mock_host_.get(), "{\"id\":0,\"result\":{\"virtualTimeBase\":1.0}}"); EXPECT_TRUE(set_up_complete_); EXPECT_FALSE(budget_expired_); @@ -98,13 +119,13 @@ namespace { class MockTask : public VirtualTimeController::RepeatingTask { public: MOCK_METHOD2(IntervalElapsed, - void(const base::TimeDelta& virtual_time, + void(base::TimeDelta virtual_time_offset, const base::Callback<void()>& continue_callback)); MOCK_METHOD3(BudgetRequested, - void(const base::TimeDelta& virtual_time, - int requested_budget_ms, + void(base::TimeDelta virtual_time_offset, + base::TimeDelta requested_budget, const base::Callback<void()>& continue_callback)); - MOCK_METHOD1(BudgetExpired, void(const base::TimeDelta& virtual_time)); + MOCK_METHOD1(BudgetExpired, void(base::TimeDelta virtual_time_offset)); }; ACTION_TEMPLATE(RunClosure, @@ -120,16 +141,19 @@ ACTION_P(RunClosure, closure) { TEST_F(VirtualTimeControllerTest, InterleavesTasksWithVirtualTime) { MockTask task; - controller_->ScheduleRepeatingTask(&task, 1000); + controller_->ScheduleRepeatingTask(&task, + base::TimeDelta::FromMilliseconds(1000)); - EXPECT_CALL(task, - BudgetRequested(base::TimeDelta::FromMilliseconds(0), 3000, _)) + EXPECT_CALL(task, BudgetRequested(base::TimeDelta::FromMilliseconds(0), + base::TimeDelta::FromMilliseconds(3000), _)) .WillOnce(RunClosure<2>()); EXPECT_CALL(*mock_host_, DispatchProtocolMessage( &client_, "{\"id\":0,\"method\":\"Emulation.setVirtualTimePolicy\"," - "\"params\":{\"budget\":1000,\"policy\":\"advance\"}}")) + "\"params\":{\"budget\":1000.0," + "\"maxVirtualTimeTaskStarvationCount\":0," + "\"policy\":\"advance\"}}")) .WillOnce(Return(true)); GrantVirtualTimeBudget(3000); @@ -137,7 +161,8 @@ TEST_F(VirtualTimeControllerTest, InterleavesTasksWithVirtualTime) { EXPECT_FALSE(set_up_complete_); EXPECT_FALSE(budget_expired_); - client_.DispatchProtocolMessage(mock_host_.get(), "{\"id\":0,\"result\":{}}"); + client_.DispatchProtocolMessage( + mock_host_.get(), "{\"id\":0,\"result\":{\"virtualTimeBase\":1.0}}"); EXPECT_TRUE(set_up_complete_); EXPECT_FALSE(budget_expired_); @@ -155,7 +180,9 @@ TEST_F(VirtualTimeControllerTest, InterleavesTasksWithVirtualTime) { &client_, base::StringPrintf( "{\"id\":%d,\"method\":\"Emulation.setVirtualTimePolicy\"," - "\"params\":{\"budget\":1000,\"policy\":\"advance\"}}", + "\"params\":{\"budget\":1000.0," + "\"maxVirtualTimeTaskStarvationCount\":0," + "\"policy\":\"advance\"}}", i * 2))) .WillOnce(Return(true)); @@ -166,7 +193,8 @@ TEST_F(VirtualTimeControllerTest, InterleavesTasksWithVirtualTime) { client_.DispatchProtocolMessage( mock_host_.get(), - base::StringPrintf("{\"id\":%d,\"result\":{}}", i * 2)); + base::StringPrintf("{\"id\":%d,\"result\":{\"virtualTimeBase\":1.0}}", + i * 2)); EXPECT_FALSE(set_up_complete_); EXPECT_FALSE(budget_expired_); @@ -183,16 +211,19 @@ TEST_F(VirtualTimeControllerTest, InterleavesTasksWithVirtualTime) { TEST_F(VirtualTimeControllerTest, CanceledTask) { MockTask task; - controller_->ScheduleRepeatingTask(&task, 1000); + controller_->ScheduleRepeatingTask(&task, + base::TimeDelta::FromMilliseconds(1000)); - EXPECT_CALL(task, - BudgetRequested(base::TimeDelta::FromMilliseconds(0), 5000, _)) + EXPECT_CALL(task, BudgetRequested(base::TimeDelta::FromMilliseconds(0), + base::TimeDelta::FromMilliseconds(5000), _)) .WillOnce(RunClosure<2>()); EXPECT_CALL(*mock_host_, DispatchProtocolMessage( &client_, "{\"id\":0,\"method\":\"Emulation.setVirtualTimePolicy\"," - "\"params\":{\"budget\":1000,\"policy\":\"advance\"}}")) + "\"params\":{\"budget\":1000.0," + "\"maxVirtualTimeTaskStarvationCount\":0," + "\"policy\":\"advance\"}}")) .WillOnce(Return(true)); GrantVirtualTimeBudget(5000); @@ -200,7 +231,8 @@ TEST_F(VirtualTimeControllerTest, CanceledTask) { EXPECT_FALSE(set_up_complete_); EXPECT_FALSE(budget_expired_); - client_.DispatchProtocolMessage(mock_host_.get(), "{\"id\":0,\"result\":{}}"); + client_.DispatchProtocolMessage( + mock_host_.get(), "{\"id\":0,\"result\":{\"virtualTimeBase\":1.0}}"); EXPECT_TRUE(set_up_complete_); EXPECT_FALSE(budget_expired_); @@ -214,7 +246,9 @@ TEST_F(VirtualTimeControllerTest, CanceledTask) { DispatchProtocolMessage( &client_, "{\"id\":2,\"method\":\"Emulation.setVirtualTimePolicy\"," - "\"params\":{\"budget\":1000,\"policy\":\"advance\"}}")) + "\"params\":{\"budget\":1000.0," + "\"maxVirtualTimeTaskStarvationCount\":0," + "\"policy\":\"advance\"}}")) .WillOnce(Return(true)); SendVirtualTimeBudgetExpiredEvent(); @@ -223,7 +257,8 @@ TEST_F(VirtualTimeControllerTest, CanceledTask) { EXPECT_FALSE(budget_expired_); client_.DispatchProtocolMessage( - mock_host_.get(), base::StringPrintf("{\"id\":2,\"result\":{}}")); + mock_host_.get(), + base::StringPrintf("{\"id\":2,\"result\":{\"virtualTimeBase\":1.0}}")); EXPECT_FALSE(set_up_complete_); EXPECT_FALSE(budget_expired_); @@ -234,7 +269,9 @@ TEST_F(VirtualTimeControllerTest, CanceledTask) { DispatchProtocolMessage( &client_, "{\"id\":4,\"method\":\"Emulation.setVirtualTimePolicy\"," - "\"params\":{\"budget\":3000,\"policy\":\"advance\"}}")) + "\"params\":{\"budget\":3000.0," + "\"maxVirtualTimeTaskStarvationCount\":0," + "\"policy\":\"advance\"}}")) .WillOnce(Return(true)); SendVirtualTimeBudgetExpiredEvent(); @@ -243,7 +280,8 @@ TEST_F(VirtualTimeControllerTest, CanceledTask) { EXPECT_FALSE(budget_expired_); client_.DispatchProtocolMessage( - mock_host_.get(), base::StringPrintf("{\"id\":4,\"result\":{}}")); + mock_host_.get(), + base::StringPrintf("{\"id\":4,\"result\":{\"virtualTimeBase\":1.0}}")); EXPECT_FALSE(set_up_complete_); EXPECT_FALSE(budget_expired_); @@ -254,4 +292,181 @@ TEST_F(VirtualTimeControllerTest, CanceledTask) { EXPECT_TRUE(budget_expired_); } +TEST_F(VirtualTimeControllerTest, MultipleTasks) { + MockTask task1; + MockTask task2; + controller_->ScheduleRepeatingTask(&task1, + base::TimeDelta::FromMilliseconds(1000)); + controller_->ScheduleRepeatingTask(&task2, + base::TimeDelta::FromMilliseconds(1000)); + + EXPECT_CALL(task1, + BudgetRequested(base::TimeDelta::FromMilliseconds(0), + base::TimeDelta::FromMilliseconds(2000), _)) + .WillOnce(RunClosure<2>()); + EXPECT_CALL(task2, + BudgetRequested(base::TimeDelta::FromMilliseconds(0), + base::TimeDelta::FromMilliseconds(2000), _)) + .WillOnce(RunClosure<2>()); + // We should only get one call to Emulation.setVirtualTimePolicy despite + // having two tasks. + EXPECT_CALL(*mock_host_, + DispatchProtocolMessage( + &client_, + "{\"id\":0,\"method\":\"Emulation.setVirtualTimePolicy\"," + "\"params\":{\"budget\":1000.0," + "\"maxVirtualTimeTaskStarvationCount\":0," + "\"policy\":\"advance\"}}")) + .WillOnce(Return(true)); + + GrantVirtualTimeBudget(2000); + EXPECT_FALSE(set_up_complete_); + EXPECT_FALSE(budget_expired_); + + client_.DispatchProtocolMessage( + mock_host_.get(), + base::StringPrintf("{\"id\":0,\"result\":{\"virtualTimeBase\":1.0}}")); + + EXPECT_TRUE(set_up_complete_); + EXPECT_FALSE(budget_expired_); +} + +class VirtualTimeTask : public VirtualTimeController::RepeatingTask { + public: + using Task = base::Callback<void(base::TimeDelta virtual_time)>; + + VirtualTimeTask(VirtualTimeController* controller, + Task budget_requested_task, + Task interval_elapsed_task, + Task budget_expired_task) + : controller_(controller), + budget_requested_task_(budget_requested_task), + interval_elapsed_task_(interval_elapsed_task), + budget_expired_task_(budget_expired_task) {} + + void IntervalElapsed( + base::TimeDelta virtual_time, + const base::Callback<void()>& continue_callback) override { + interval_elapsed_task_.Run(virtual_time); + continue_callback.Run(); + } + + void BudgetRequested( + base::TimeDelta virtual_time, + base::TimeDelta requested_budget_ms, + const base::Callback<void()>& continue_callback) override { + budget_requested_task_.Run(virtual_time); + continue_callback.Run(); + } + + void BudgetExpired(base::TimeDelta virtual_time) override { + budget_expired_task_.Run(virtual_time); + }; + + VirtualTimeController* controller_; // NOT OWNED + Task budget_requested_task_; + Task interval_elapsed_task_; + Task budget_expired_task_; +}; + +TEST_F(VirtualTimeControllerTest, ReentrantTask) { +#if defined(__clang__) + std::vector<std::string> log; + VirtualTimeTask task_b( + controller_.get(), + base::Bind( + [](std::vector<std::string>* log, base::TimeDelta virtual_time) { + log->push_back(base::StringPrintf( + "B: budget requested @ %d", + static_cast<int>(virtual_time.InMilliseconds()))); + }, + &log), + base::Bind( + [](std::vector<std::string>* log, VirtualTimeController* controller, + VirtualTimeTask* task_b, base::TimeDelta virtual_time) { + log->push_back(base::StringPrintf( + "B: interval elapsed @ %d", + static_cast<int>(virtual_time.InMilliseconds()))); + controller->CancelRepeatingTask(task_b); + }, + &log, controller_.get(), &task_b), + base::Bind( + [](std::vector<std::string>* log, base::TimeDelta virtual_time) { + log->push_back(base::StringPrintf( + "B: budget expired @ %d", + static_cast<int>(virtual_time.InMilliseconds()))); + }, + &log)); + + VirtualTimeTask task_a( + controller_.get(), + base::Bind( + [](std::vector<std::string>* log, base::TimeDelta virtual_time) { + log->push_back(base::StringPrintf( + "A: budget requested @ %d", + static_cast<int>(virtual_time.InMilliseconds()))); + }, + &log), + base::Bind( + [](std::vector<std::string>* log, VirtualTimeController* controller, + VirtualTimeTask* task_a, VirtualTimeTask* task_b, + base::TimeDelta virtual_time) { + log->push_back(base::StringPrintf( + "A: interval elapsed @ %d", + static_cast<int>(virtual_time.InMilliseconds()))); + controller->CancelRepeatingTask(task_a); + controller->ScheduleRepeatingTask( + task_b, base::TimeDelta::FromMilliseconds(1500)); + }, + &log, controller_.get(), &task_a, &task_b), + base::Bind( + [](std::vector<std::string>* log, base::TimeDelta virtual_time) { + log->push_back(base::StringPrintf( + "A: budget expired @ %d", + static_cast<int>(virtual_time.InMilliseconds()))); + }, + &log)); + + controller_->ScheduleRepeatingTask(&task_a, + base::TimeDelta::FromMilliseconds(1000)); + + EXPECT_CALL(*mock_host_, + DispatchProtocolMessage( + &client_, + "{\"id\":0,\"method\":\"Emulation.setVirtualTimePolicy\"," + "\"params\":{\"budget\":1000.0," + "\"maxVirtualTimeTaskStarvationCount\":0," + "\"policy\":\"advance\"}}")) + .WillOnce(Return(true)); + + GrantVirtualTimeBudget(6000); + Mock::VerifyAndClearExpectations(&mock_host_); + + EXPECT_CALL(*mock_host_, + DispatchProtocolMessage( + &client_, + "{\"id\":2,\"method\":\"Emulation.setVirtualTimePolicy\"," + "\"params\":{\"budget\":1500.0," + "\"maxVirtualTimeTaskStarvationCount\":0," + "\"policy\":\"advance\"}}")) + .WillOnce(Return(true)); + SendVirtualTimeBudgetExpiredEvent(); + Mock::VerifyAndClearExpectations(&mock_host_); + + EXPECT_CALL(*mock_host_, + DispatchProtocolMessage( + &client_, + "{\"id\":4,\"method\":\"Emulation.setVirtualTimePolicy\"," + "\"params\":{\"budget\":3500.0," + "\"maxVirtualTimeTaskStarvationCount\":0," + "\"policy\":\"advance\"}}")) + .WillOnce(Return(true)); + SendVirtualTimeBudgetExpiredEvent(); + + EXPECT_THAT( + log, ElementsAre("A: budget requested @ 0", "A: interval elapsed @ 1000", + "B: interval elapsed @ 2500")); +#endif +} + } // namespace headless |