diff options
author | Allan Sandfeld Jensen <allan.jensen@theqtcompany.com> | 2016-07-14 17:41:05 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2016-08-04 12:37:36 +0000 |
commit | 399c965b6064c440ddcf4015f5f8e9d131c7a0a6 (patch) | |
tree | 6b06b60ff365abef0e13b3503d593a0df48d20e8 /chromium/headless | |
parent | 7366110654eec46f21b6824f302356426f48cd74 (diff) | |
download | qtwebengine-chromium-399c965b6064c440ddcf4015f5f8e9d131c7a0a6.tar.gz |
BASELINE: Update Chromium to 52.0.2743.76 and Ninja to 1.7.1
Change-Id: I382f51b959689505a60f8b707255ecb344f7d8b4
Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/headless')
45 files changed, 3867 insertions, 364 deletions
diff --git a/chromium/headless/BUILD.gn b/chromium/headless/BUILD.gn index c035c2599f4..e1a853ee47c 100644 --- a/chromium/headless/BUILD.gn +++ b/chromium/headless/BUILD.gn @@ -54,8 +54,156 @@ grit("headless_lib_resources_grit") { ] } +action("gen_devtools_client_api") { + script = "//headless/lib/browser/client_api_generator.py" + + inputs = [ + "//third_party/WebKit/Source/devtools/protocol.json", + ] + + outputs = [ + "$target_gen_dir/public/domains/accessibility.cc", + "$target_gen_dir/public/domains/accessibility.h", + "$target_gen_dir/public/domains/animation.cc", + "$target_gen_dir/public/domains/animation.h", + "$target_gen_dir/public/domains/application_cache.cc", + "$target_gen_dir/public/domains/application_cache.h", + "$target_gen_dir/public/domains/cache_storage.cc", + "$target_gen_dir/public/domains/cache_storage.h", + "$target_gen_dir/public/domains/console.cc", + "$target_gen_dir/public/domains/console.h", + "$target_gen_dir/public/domains/css.cc", + "$target_gen_dir/public/domains/css.h", + "$target_gen_dir/public/domains/database.cc", + "$target_gen_dir/public/domains/database.h", + "$target_gen_dir/public/domains/debugger.cc", + "$target_gen_dir/public/domains/debugger.h", + "$target_gen_dir/public/domains/device_orientation.cc", + "$target_gen_dir/public/domains/device_orientation.h", + "$target_gen_dir/public/domains/dom_debugger.cc", + "$target_gen_dir/public/domains/dom_debugger.h", + "$target_gen_dir/public/domains/dom.cc", + "$target_gen_dir/public/domains/dom.h", + "$target_gen_dir/public/domains/dom_storage.cc", + "$target_gen_dir/public/domains/dom_storage.h", + "$target_gen_dir/public/domains/emulation.cc", + "$target_gen_dir/public/domains/emulation.h", + "$target_gen_dir/public/domains/heap_profiler.cc", + "$target_gen_dir/public/domains/heap_profiler.h", + "$target_gen_dir/public/domains/indexeddb.cc", + "$target_gen_dir/public/domains/indexeddb.h", + "$target_gen_dir/public/domains/input.cc", + "$target_gen_dir/public/domains/input.h", + "$target_gen_dir/public/domains/inspector.cc", + "$target_gen_dir/public/domains/inspector.h", + "$target_gen_dir/public/domains/io.cc", + "$target_gen_dir/public/domains/io.h", + "$target_gen_dir/public/domains/layer_tree.cc", + "$target_gen_dir/public/domains/layer_tree.h", + "$target_gen_dir/public/domains/memory.cc", + "$target_gen_dir/public/domains/memory.h", + "$target_gen_dir/public/domains/network.cc", + "$target_gen_dir/public/domains/network.h", + "$target_gen_dir/public/domains/page.cc", + "$target_gen_dir/public/domains/page.h", + "$target_gen_dir/public/domains/profiler.cc", + "$target_gen_dir/public/domains/profiler.h", + "$target_gen_dir/public/domains/rendering.cc", + "$target_gen_dir/public/domains/rendering.h", + "$target_gen_dir/public/domains/runtime.cc", + "$target_gen_dir/public/domains/runtime.h", + "$target_gen_dir/public/domains/security.cc", + "$target_gen_dir/public/domains/security.h", + "$target_gen_dir/public/domains/service_worker.cc", + "$target_gen_dir/public/domains/service_worker.h", + "$target_gen_dir/public/domains/tracing.cc", + "$target_gen_dir/public/domains/tracing.h", + "$target_gen_dir/public/domains/type_conversions.h", + "$target_gen_dir/public/domains/types.cc", + "$target_gen_dir/public/domains/types.h", + "$target_gen_dir/public/domains/worker.cc", + "$target_gen_dir/public/domains/worker.h", + ] + + sources = [ + "lib/browser/domain_cc.template", + "lib/browser/domain_h.template", + "lib/browser/type_conversions_h.template", + "lib/browser/types_cc.template", + "lib/browser/types_h.template", + ] + + args = [ + "--protocol", + rebase_path(inputs[0], root_build_dir), + "--output_dir", + rebase_path(target_gen_dir) + "/public/domains", + ] +} + static_library("headless_lib") { sources = [ + "$target_gen_dir/public/domains/accessibility.cc", + "$target_gen_dir/public/domains/accessibility.h", + "$target_gen_dir/public/domains/animation.cc", + "$target_gen_dir/public/domains/animation.h", + "$target_gen_dir/public/domains/application_cache.cc", + "$target_gen_dir/public/domains/application_cache.h", + "$target_gen_dir/public/domains/cache_storage.cc", + "$target_gen_dir/public/domains/cache_storage.h", + "$target_gen_dir/public/domains/console.cc", + "$target_gen_dir/public/domains/console.h", + "$target_gen_dir/public/domains/css.cc", + "$target_gen_dir/public/domains/css.h", + "$target_gen_dir/public/domains/database.cc", + "$target_gen_dir/public/domains/database.h", + "$target_gen_dir/public/domains/debugger.cc", + "$target_gen_dir/public/domains/debugger.h", + "$target_gen_dir/public/domains/device_orientation.cc", + "$target_gen_dir/public/domains/device_orientation.h", + "$target_gen_dir/public/domains/dom.cc", + "$target_gen_dir/public/domains/dom.h", + "$target_gen_dir/public/domains/dom_debugger.cc", + "$target_gen_dir/public/domains/dom_debugger.h", + "$target_gen_dir/public/domains/dom_storage.cc", + "$target_gen_dir/public/domains/dom_storage.h", + "$target_gen_dir/public/domains/emulation.cc", + "$target_gen_dir/public/domains/emulation.h", + "$target_gen_dir/public/domains/heap_profiler.cc", + "$target_gen_dir/public/domains/heap_profiler.h", + "$target_gen_dir/public/domains/indexeddb.cc", + "$target_gen_dir/public/domains/indexeddb.h", + "$target_gen_dir/public/domains/input.cc", + "$target_gen_dir/public/domains/input.h", + "$target_gen_dir/public/domains/inspector.cc", + "$target_gen_dir/public/domains/inspector.h", + "$target_gen_dir/public/domains/io.cc", + "$target_gen_dir/public/domains/io.h", + "$target_gen_dir/public/domains/layer_tree.cc", + "$target_gen_dir/public/domains/layer_tree.h", + "$target_gen_dir/public/domains/memory.cc", + "$target_gen_dir/public/domains/memory.h", + "$target_gen_dir/public/domains/network.cc", + "$target_gen_dir/public/domains/network.h", + "$target_gen_dir/public/domains/page.cc", + "$target_gen_dir/public/domains/page.h", + "$target_gen_dir/public/domains/profiler.cc", + "$target_gen_dir/public/domains/profiler.h", + "$target_gen_dir/public/domains/rendering.cc", + "$target_gen_dir/public/domains/rendering.h", + "$target_gen_dir/public/domains/runtime.cc", + "$target_gen_dir/public/domains/runtime.h", + "$target_gen_dir/public/domains/security.cc", + "$target_gen_dir/public/domains/security.h", + "$target_gen_dir/public/domains/service_worker.cc", + "$target_gen_dir/public/domains/service_worker.h", + "$target_gen_dir/public/domains/tracing.cc", + "$target_gen_dir/public/domains/tracing.h", + "$target_gen_dir/public/domains/type_conversions.h", + "$target_gen_dir/public/domains/types.cc", + "$target_gen_dir/public/domains/types.h", + "$target_gen_dir/public/domains/worker.cc", + "$target_gen_dir/public/domains/worker.h", "lib/browser/headless_browser_context.cc", "lib/browser/headless_browser_context.h", "lib/browser/headless_browser_impl.cc", @@ -66,12 +214,16 @@ static_library("headless_lib") { "lib/browser/headless_content_browser_client.h", "lib/browser/headless_devtools.cc", "lib/browser/headless_devtools.h", + "lib/browser/headless_devtools_client_impl.cc", + "lib/browser/headless_devtools_client_impl.h", "lib/browser/headless_screen.cc", "lib/browser/headless_screen.h", "lib/browser/headless_url_request_context_getter.cc", "lib/browser/headless_url_request_context_getter.h", "lib/browser/headless_web_contents_impl.cc", "lib/browser/headless_web_contents_impl.h", + "lib/browser/headless_window_tree_client.cc", + "lib/browser/headless_window_tree_client.h", "lib/headless_content_client.cc", "lib/headless_content_client.h", "lib/headless_content_main_delegate.cc", @@ -82,12 +234,19 @@ static_library("headless_lib") { "lib/utility/headless_content_utility_client.h", "public/headless_browser.cc", "public/headless_browser.h", + "public/headless_devtools_client.h", + "public/headless_devtools_host.h", + "public/headless_devtools_target.h", "public/headless_export.h", "public/headless_web_contents.h", - "public/util/maybe.h", + "public/internal/message_dispatcher.h", + "public/internal/value_conversions.h", + "public/util/error_reporter.cc", + "public/util/error_reporter.h", ] deps = [ + ":gen_devtools_client_api", ":pak", "//base", "//components/devtools_http_handler", @@ -101,6 +260,7 @@ static_library("headless_lib") { "//ui/aura", "//ui/base", "//ui/compositor", + "//ui/display", "//ui/ozone", "//url", ] @@ -110,6 +270,7 @@ group("headless_tests") { testonly = true deps = [ + ":client_api_generator_tests", ":headless_browsertests", ":headless_unittests", ] @@ -117,19 +278,39 @@ group("headless_tests") { test("headless_unittests") { sources = [ - "public/util/maybe_unittest.cc", + "public/domains/types_unittest.cc", + "public/util/error_reporter_unittest.cc", ] deps = [ + ":headless_lib", "//base/test:run_all_unittests", "//base/test:test_support", "//testing/gtest", ] } +action("client_api_generator_tests") { + _stamp = "$target_gen_dir/client_api_generator_unittests.stamp" + inputs = [ + "lib/browser/client_api_generator.py", + "lib/browser/client_api_generator_unittest.py", + ] + outputs = [ + _stamp, + ] + + script = "lib/browser/client_api_generator_unittest.py" + args = [ + "--stamp", + rebase_path(_stamp, root_build_dir), + ] +} + test("headless_browsertests") { sources = [ "lib/headless_browser_browsertest.cc", + "lib/headless_devtools_client_browsertest.cc", "lib/headless_web_contents_browsertest.cc", "test/headless_browser_test.cc", "test/headless_browser_test.h", @@ -149,8 +330,6 @@ test("headless_browsertests") { } executable("headless_shell") { - testonly = true - sources = [ "app/headless_shell.cc", "app/headless_shell_switches.cc", diff --git a/chromium/headless/DEPS b/chromium/headless/DEPS index b251cd96553..2e747057cbf 100644 --- a/chromium/headless/DEPS +++ b/chromium/headless/DEPS @@ -4,6 +4,7 @@ include_rules = [ "+net", "+ui/base", "+ui/base/resource", + "+ui/display", "+ui/gfx", "+ui/gfx/geometry", "+ui/ozone/public", diff --git a/chromium/headless/README.md b/chromium/headless/README.md index 0748cfd8f6a..9e3c7b02eba 100644 --- a/chromium/headless/README.md +++ b/chromium/headless/README.md @@ -5,6 +5,9 @@ environment. Expected use cases include loading web pages, extracting metadata (e.g., the DOM) and generating bitmaps from page contents -- using all the modern web platform features provided by Chromium and Blink. +See the [architecture design doc](https://docs.google.com/document/d/11zIkKkLBocofGgoTeeyibB2TZ_k7nR78v7kNelCatUE) +for more information. + ## Headless shell The headless shell is a sample application which demonstrates the use of the @@ -52,10 +55,14 @@ The main embedder API classes are: - `SetProxyServer` - Configures an HTTP/HTTPS proxy server to be used for accessing the network. -## Client API +## Client/DevTools API The headless client API is used to drive the browser and interact with loaded web pages. Its main classes are: - `HeadlessBrowser` - Represents the global headless browser instance. - `HeadlessWebContents` - Represents a single "tab" within the browser. +- `HeadlessDevToolsClient` - Provides a C++ interface for inspecting and + controlling a tab. The API functions corresponds to [DevTools commands](https://developer.chrome.com/devtools/docs/debugger-protocol). + See the [client API documentation](https://docs.google.com/document/d/1rlqcp8nk-ZQvldNJWdbaMbwfDbJoOXvahPCDoPGOwhQ/edit#) + for more information. diff --git a/chromium/headless/app/headless_shell.cc b/chromium/headless/app/headless_shell.cc index 1031dbd5446..25c823d541c 100644 --- a/chromium/headless/app/headless_shell.cc +++ b/chromium/headless/app/headless_shell.cc @@ -2,24 +2,33 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <iostream> #include <memory> #include "base/bind.h" #include "base/callback.h" #include "base/command_line.h" +#include "base/json/json_writer.h" #include "base/location.h" #include "base/memory/ref_counted.h" #include "base/numerics/safe_conversions.h" #include "base/strings/string_number_conversions.h" #include "content/public/common/content_switches.h" #include "headless/app/headless_shell_switches.h" +#include "headless/public/domains/page.h" +#include "headless/public/domains/runtime.h" #include "headless/public/headless_browser.h" +#include "headless/public/headless_devtools_client.h" +#include "headless/public/headless_devtools_target.h" #include "headless/public/headless_web_contents.h" #include "net/base/ip_address.h" #include "ui/gfx/geometry/size.h" using headless::HeadlessBrowser; +using headless::HeadlessDevToolsClient; using headless::HeadlessWebContents; +namespace page = headless::page; +namespace runtime = headless::runtime; namespace { // Address where to listen to incoming DevTools connections. @@ -27,13 +36,13 @@ const char kDevToolsHttpServerAddress[] = "127.0.0.1"; } // A sample application which demonstrates the use of the headless API. -class HeadlessShell : public HeadlessWebContents::Observer { +class HeadlessShell : public HeadlessWebContents::Observer, page::Observer { public: - HeadlessShell() : browser_(nullptr) {} - ~HeadlessShell() override { - if (web_contents_) - web_contents_->RemoveObserver(this); - } + HeadlessShell() + : browser_(nullptr), + devtools_client_(HeadlessDevToolsClient::Create()), + processed_page_ready_(false) {} + ~HeadlessShell() override {} void OnStart(HeadlessBrowser* browser) { browser_ = browser; @@ -42,13 +51,13 @@ class HeadlessShell : public HeadlessWebContents::Observer { base::CommandLine::ForCurrentProcess()->GetArgs(); const char kDefaultUrl[] = "about:blank"; - GURL url; if (args.empty() || args[0].empty()) { - url = GURL(kDefaultUrl); + url_ = GURL(kDefaultUrl); } else { - url = GURL(args[0]); + url_ = GURL(args[0]); } - web_contents_ = browser->CreateWebContents(url, gfx::Size(800, 600)); + + web_contents_ = browser->CreateWebContents(url_, gfx::Size(800, 600)); if (!web_contents_) { LOG(ERROR) << "Navigation failed"; browser_->Shutdown(); @@ -57,23 +66,133 @@ class HeadlessShell : public HeadlessWebContents::Observer { web_contents_->AddObserver(this); } - void ShutdownIfNeeded() { - const base::CommandLine& command_line = - *base::CommandLine::ForCurrentProcess(); - if (!command_line.HasSwitch(switches::kRemoteDebuggingPort)) { - web_contents_ = nullptr; - browser_->Shutdown(); + void Shutdown() { + if (!web_contents_) + return; + if (!RemoteDebuggingEnabled()) { + devtools_client_->GetPage()->RemoveObserver(this); + web_contents_->GetDevToolsTarget()->DetachClient(devtools_client_.get()); } + web_contents_->RemoveObserver(this); + web_contents_ = nullptr; + browser_->Shutdown(); } // HeadlessWebContents::Observer implementation: - void DocumentOnLoadCompletedInMainFrame() override { - ShutdownIfNeeded(); + void DevToolsTargetReady() override { + if (RemoteDebuggingEnabled()) + return; + web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get()); + devtools_client_->GetPage()->AddObserver(this); + devtools_client_->GetPage()->Enable(); + // Check if the document had already finished loading by the time we + // attached. + PollReadyState(); + // TODO(skyostil): Implement more features to demonstrate the devtools API. + } + + void PollReadyState() { + // We need to check the current location in addition to the ready state to + // be sure the expected page is ready. + devtools_client_->GetRuntime()->Evaluate( + "document.readyState + ' ' + document.location.href", + base::Bind(&HeadlessShell::OnReadyState, base::Unretained(this))); + } + + void OnReadyState(std::unique_ptr<runtime::EvaluateResult> result) { + std::string ready_state_and_url; + if (result->GetResult()->GetValue()->GetAsString(&ready_state_and_url)) { + std::stringstream stream(ready_state_and_url); + std::string ready_state; + std::string url; + stream >> ready_state; + stream >> url; + + if (ready_state == "complete" && + (url_.spec() == url || url != "about:blank")) { + OnPageReady(); + return; + } + } + } + + // page::Observer implementation: + void OnLoadEventFired(const page::LoadEventFiredParams& params) override { + OnPageReady(); + } + + void OnPageReady() { + if (processed_page_ready_) + return; + processed_page_ready_ = true; + + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + headless::switches::kDumpDom)) { + FetchDom(); + } else if (base::CommandLine::ForCurrentProcess()->HasSwitch( + headless::switches::kRepl)) { + std::cout + << "Type a Javascript expression to evaluate or \"quit\" to exit." + << std::endl; + InputExpression(); + } else { + Shutdown(); + } + } + + void FetchDom() { + devtools_client_->GetRuntime()->Evaluate( + "document.body.innerHTML", + base::Bind(&HeadlessShell::OnDomFetched, base::Unretained(this))); + } + + void OnDomFetched(std::unique_ptr<runtime::EvaluateResult> result) { + if (result->GetWasThrown()) { + LOG(ERROR) << "Failed to evaluate document.body.innerHTML"; + } else { + std::string dom; + if (result->GetResult()->GetValue()->GetAsString(&dom)) { + std::cout << dom << std::endl; + } + } + Shutdown(); + } + + void InputExpression() { + // Note that a real system should read user input asynchronously, because + // otherwise all other browser activity is suspended (e.g., page loading). + std::string expression; + std::cout << ">>> "; + std::getline(std::cin, expression); + if (std::cin.bad() || std::cin.eof() || expression == "quit") { + Shutdown(); + return; + } + devtools_client_->GetRuntime()->Evaluate( + expression, + base::Bind(&HeadlessShell::OnExpressionResult, base::Unretained(this))); + } + + void OnExpressionResult(std::unique_ptr<runtime::EvaluateResult> result) { + std::unique_ptr<base::Value> value = result->Serialize(); + std::string result_json; + base::JSONWriter::Write(*value, &result_json); + std::cout << result_json << std::endl; + InputExpression(); + } + + bool RemoteDebuggingEnabled() const { + const base::CommandLine& command_line = + *base::CommandLine::ForCurrentProcess(); + return command_line.HasSwitch(switches::kRemoteDebuggingPort); } private: + GURL url_; HeadlessBrowser* browser_; // Not owned. - std::unique_ptr<HeadlessWebContents> web_contents_; + std::unique_ptr<HeadlessDevToolsClient> devtools_client_; + HeadlessWebContents* web_contents_; + bool processed_page_ready_; DISALLOW_COPY_AND_ASSIGN(HeadlessShell); }; @@ -85,18 +204,29 @@ int main(int argc, const char** argv) { // Enable devtools if requested. base::CommandLine command_line(argc, argv); if (command_line.HasSwitch(switches::kRemoteDebuggingPort)) { + std::string address = kDevToolsHttpServerAddress; + if (command_line.HasSwitch(headless::switches::kRemoteDebuggingAddress)) { + address = command_line.GetSwitchValueASCII( + headless::switches::kRemoteDebuggingAddress); + net::IPAddress parsed_address; + if (!net::ParseURLHostnameToAddress(address, &parsed_address)) { + LOG(ERROR) << "Invalid devtools server address"; + return EXIT_FAILURE; + } + } int parsed_port; std::string port_str = command_line.GetSwitchValueASCII(switches::kRemoteDebuggingPort); - if (base::StringToInt(port_str, &parsed_port) && - base::IsValueInRangeForNumericType<uint16_t>(parsed_port)) { - net::IPAddress devtools_address; - bool result = - devtools_address.AssignFromIPLiteral(kDevToolsHttpServerAddress); - DCHECK(result); - builder.EnableDevToolsServer(net::IPEndPoint( - devtools_address, base::checked_cast<uint16_t>(parsed_port))); + if (!base::StringToInt(port_str, &parsed_port) || + !base::IsValueInRangeForNumericType<uint16_t>(parsed_port)) { + LOG(ERROR) << "Invalid devtools server port"; + return EXIT_FAILURE; } + net::IPAddress devtools_address; + bool result = devtools_address.AssignFromIPLiteral(address); + DCHECK(result); + builder.EnableDevToolsServer(net::IPEndPoint( + devtools_address, base::checked_cast<uint16_t>(parsed_port))); } if (command_line.HasSwitch(headless::switches::kProxyServer)) { @@ -111,6 +241,11 @@ int main(int argc, const char** argv) { builder.SetProxyServer(parsed_proxy_server); } + if (command_line.HasSwitch(switches::kHostResolverRules)) { + builder.SetHostResolverRules( + command_line.GetSwitchValueASCII(switches::kHostResolverRules)); + } + return HeadlessBrowserMain( builder.Build(), base::Bind(&HeadlessShell::OnStart, base::Unretained(&shell))); diff --git a/chromium/headless/app/headless_shell_switches.cc b/chromium/headless/app/headless_shell_switches.cc index c3c6ad7b221..f8ee4343376 100644 --- a/chromium/headless/app/headless_shell_switches.cc +++ b/chromium/headless/app/headless_shell_switches.cc @@ -7,9 +7,22 @@ namespace headless { namespace switches { +// Instructs headless_shell to print document.body.innerHTML to stdout. +const char kDumpDom[] = "dump-dom"; + // Uses a specified proxy server, overrides system settings. This switch only // affects HTTP and HTTPS requests. const char kProxyServer[] = "proxy-server"; +// Use the given address instead of the default loopback for accepting remote +// debugging connections. Should be used together with --remote-debugging-port. +// Note that the remote debugging protocol does not perform any authentication, +// so exposing it too widely can be a security risk. +const char kRemoteDebuggingAddress[] = "remote-debugging-address"; + +// Runs a read-eval-print loop that allows the user to evaluate Javascript +// expressions. +const char kRepl[] = "repl"; + } // namespace switches } // namespace headless diff --git a/chromium/headless/app/headless_shell_switches.h b/chromium/headless/app/headless_shell_switches.h index dbca97c9d3b..d4a8c6bf7f7 100644 --- a/chromium/headless/app/headless_shell_switches.h +++ b/chromium/headless/app/headless_shell_switches.h @@ -7,7 +7,10 @@ namespace headless { namespace switches { +extern const char kDumpDom[]; extern const char kProxyServer[]; +extern const char kRemoteDebuggingAddress[]; +extern const char kRepl[]; } // namespace switches } // namespace headless diff --git a/chromium/headless/lib/browser/client_api_generator.py b/chromium/headless/lib/browser/client_api_generator.py new file mode 100644 index 00000000000..40f1206d17b --- /dev/null +++ b/chromium/headless/lib/browser/client_api_generator.py @@ -0,0 +1,422 @@ +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import argparse +import os.path +import sys +import re +try: + import json +except ImportError: + import simplejson as json + +# Path handling for libraries and templates +# Paths have to be normalized because Jinja uses the exact template path to +# determine the hash used in the cache filename, and we need a pre-caching step +# to be concurrency-safe. Use absolute path because __file__ is absolute if +# module is imported, and relative if executed directly. +# If paths differ between pre-caching and individual file compilation, the cache +# is regenerated, which causes a race condition and breaks concurrent build, +# since some compile processes will try to read the partially written cache. +module_path, module_filename = os.path.split(os.path.realpath(__file__)) +third_party_dir = os.path.normpath(os.path.join( + module_path, os.pardir, os.pardir, os.pardir, 'third_party')) +templates_dir = module_path + +# jinja2 is in chromium's third_party directory. +# Insert at 1 so at front to override system libraries, and +# after path[0] == invoking script dir +sys.path.insert(1, third_party_dir) +import jinja2 + + +def ParseArguments(args): + """Parses command line arguments and returns a (json_api, output_dir) tuple. + """ + cmdline_parser = argparse.ArgumentParser() + cmdline_parser.add_argument('--protocol', required=True) + cmdline_parser.add_argument('--output_dir', required=True) + + args = cmdline_parser.parse_args(args) + with open(args.protocol, 'r') as f: + return json.load(f), args.output_dir + + +def ToTitleCase(name): + return name[:1].upper() + name[1:] + + +def DashToCamelCase(word): + return ''.join(ToTitleCase(x) for x in word.split('-')) + + +def CamelCaseToHackerStyle(name): + # Do two passes to insert '_' chars to deal with overlapping matches (e.g., + # 'LoLoLoL'). + name = re.sub(r'([^_])([A-Z][a-z]+?)', r'\1_\2', name) + name = re.sub(r'([^_])([A-Z][a-z]+?)', r'\1_\2', name) + return name.lower() + + +def MangleEnum(value): + # Rename NULL enumeration values to avoid a clash with the actual NULL. + return 'NONE' if value == 'NULL' else value + + +def InitializeJinjaEnv(cache_dir): + jinja_env = jinja2.Environment( + loader=jinja2.FileSystemLoader(templates_dir), + # Bytecode cache is not concurrency-safe unless pre-cached: + # if pre-cached this is read-only, but writing creates a race condition. + bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir), + keep_trailing_newline=True, # Newline-terminate generated files. + lstrip_blocks=True, # So we can indent control flow tags. + trim_blocks=True) + jinja_env.filters.update({ + 'to_title_case': ToTitleCase, + 'dash_to_camelcase': DashToCamelCase, + 'camelcase_to_hacker_style': CamelCaseToHackerStyle, + 'mangle_enum': MangleEnum, + }) + jinja_env.add_extension('jinja2.ext.loopcontrols') + return jinja_env + + +def PatchFullQualifiedRefs(json_api): + def PatchFullQualifiedRefsInDomain(json, domain_name): + if isinstance(json, list): + for item in json: + PatchFullQualifiedRefsInDomain(item, domain_name) + + if not isinstance(json, dict): + return + for key in json: + if key != '$ref': + PatchFullQualifiedRefsInDomain(json[key], domain_name) + continue + if not '.' in json['$ref']: + json['$ref'] = domain_name + '.' + json['$ref'] + + for domain in json_api['domains']: + PatchFullQualifiedRefsInDomain(domain, domain['domain']) + + +def CreateUserTypeDefinition(domain, type): + namespace = CamelCaseToHackerStyle(domain['domain']) + return { + 'return_type': 'std::unique_ptr<headless::%s::%s>' % ( + namespace, type['id']), + 'pass_type': 'std::unique_ptr<headless::%s::%s>' % (namespace, type['id']), + 'to_raw_type': '*%s', + 'to_raw_return_type': '%s.get()', + 'to_pass_type': 'std::move(%s)', + 'type': 'std::unique_ptr<headless::%s::%s>' % (namespace, type['id']), + 'raw_type': 'headless::%s::%s' % (namespace, type['id']), + 'raw_pass_type': 'headless::%s::%s*' % (namespace, type['id']), + 'raw_return_type': 'const headless::%s::%s*' % (namespace, type['id']), + } + + +def CreateEnumTypeDefinition(domain_name, type): + namespace = CamelCaseToHackerStyle(domain_name) + return { + 'return_type': 'headless::%s::%s' % (namespace, type['id']), + 'pass_type': 'headless::%s::%s' % (namespace, type['id']), + 'to_raw_type': '%s', + 'to_raw_return_type': '%s', + 'to_pass_type': '%s', + 'type': 'headless::%s::%s' % (namespace, type['id']), + 'raw_type': 'headless::%s::%s' % (namespace, type['id']), + 'raw_pass_type': 'headless::%s::%s' % (namespace, type['id']), + 'raw_return_type': 'headless::%s::%s' % (namespace, type['id']), + } + + +def CreateObjectTypeDefinition(): + return { + 'return_type': 'std::unique_ptr<base::DictionaryValue>', + 'pass_type': 'std::unique_ptr<base::DictionaryValue>', + 'to_raw_type': '*%s', + 'to_raw_return_type': '%s.get()', + 'to_pass_type': 'std::move(%s)', + 'type': 'std::unique_ptr<base::DictionaryValue>', + 'raw_type': 'base::DictionaryValue', + 'raw_pass_type': 'base::DictionaryValue*', + 'raw_return_type': 'const base::DictionaryValue*', + } + + +def WrapObjectTypeDefinition(type): + id = type.get('id', 'base::Value') + return { + 'return_type': 'std::unique_ptr<%s>' % id, + 'pass_type': 'std::unique_ptr<%s>' % id, + 'to_raw_type': '*%s', + 'to_raw_return_type': '%s.get()', + 'to_pass_type': 'std::move(%s)', + 'type': 'std::unique_ptr<%s>' % id, + 'raw_type': id, + 'raw_pass_type': '%s*' % id, + 'raw_return_type': 'const %s*' % id, + } + + +def CreateAnyTypeDefinition(): + return { + 'return_type': 'std::unique_ptr<base::Value>', + 'pass_type': 'std::unique_ptr<base::Value>', + 'to_raw_type': '*%s', + 'to_raw_return_type': '%s.get()', + 'to_pass_type': 'std::move(%s)', + 'type': 'std::unique_ptr<base::Value>', + 'raw_type': 'base::Value', + 'raw_pass_type': 'base::Value*', + 'raw_return_type': 'const base::Value*', + } + + +def CreateStringTypeDefinition(domain): + return { + 'return_type': 'std::string', + 'pass_type': 'const std::string&', + 'to_pass_type': '%s', + 'to_raw_type': '%s', + 'to_raw_return_type': '%s', + 'type': 'std::string', + 'raw_type': 'std::string', + 'raw_pass_type': 'const std::string&', + 'raw_return_type': 'std::string', + } + + +def CreatePrimitiveTypeDefinition(type): + typedefs = { + 'number': 'double', + 'integer': 'int', + 'boolean': 'bool', + 'string': 'std::string', + } + return { + 'return_type': typedefs[type], + 'pass_type': typedefs[type], + 'to_pass_type': '%s', + 'to_raw_type': '%s', + 'to_raw_return_type': '%s', + 'type': typedefs[type], + 'raw_type': typedefs[type], + 'raw_pass_type': typedefs[type], + 'raw_return_type': typedefs[type], + } + + +type_definitions = {} +type_definitions['number'] = CreatePrimitiveTypeDefinition('number') +type_definitions['integer'] = CreatePrimitiveTypeDefinition('integer') +type_definitions['boolean'] = CreatePrimitiveTypeDefinition('boolean') +type_definitions['string'] = CreatePrimitiveTypeDefinition('string') +type_definitions['object'] = CreateObjectTypeDefinition() +type_definitions['any'] = CreateAnyTypeDefinition() + + +def WrapArrayDefinition(type): + return { + 'return_type': 'std::vector<%s>' % type['type'], + 'pass_type': 'std::vector<%s>' % type['type'], + 'to_raw_type': '%s', + 'to_raw_return_type': '&%s', + 'to_pass_type': 'std::move(%s)', + 'type': 'std::vector<%s>' % type['type'], + 'raw_type': 'std::vector<%s>' % type['type'], + 'raw_pass_type': 'std::vector<%s>*' % type['type'], + 'raw_return_type': 'const std::vector<%s>*' % type['type'], + } + + +def CreateTypeDefinitions(json_api): + for domain in json_api['domains']: + if not ('types' in domain): + continue + for type in domain['types']: + if type['type'] == 'object': + if 'properties' in type: + type_definitions[domain['domain'] + '.' + type['id']] = ( + CreateUserTypeDefinition(domain, type)) + else: + type_definitions[domain['domain'] + '.' + type['id']] = ( + CreateObjectTypeDefinition()) + elif type['type'] == 'array': + items_type = type['items']['type'] + type_definitions[domain['domain'] + '.' + type['id']] = ( + WrapArrayDefinition(type_definitions[items_type])) + elif 'enum' in type: + type_definitions[domain['domain'] + '.' + type['id']] = ( + CreateEnumTypeDefinition(domain['domain'], type)) + type['$ref'] = domain['domain'] + '.' + type['id'] + elif type['type'] == 'any': + type_definitions[domain['domain'] + '.' + type['id']] = ( + CreateAnyTypeDefinition()) + else: + type_definitions[domain['domain'] + '.' + type['id']] = ( + CreatePrimitiveTypeDefinition(type['type'])) + + +def TypeDefinition(name): + return type_definitions[name] + + +def ResolveType(property): + if '$ref' in property: + return type_definitions[property['$ref']] + elif property['type'] == 'object': + return WrapObjectTypeDefinition(property) + elif property['type'] == 'array': + return WrapArrayDefinition(ResolveType(property['items'])) + return type_definitions[property['type']] + + +def JoinArrays(dict, keys): + result = [] + for key in keys: + if key in dict: + result += dict[key] + return result + + +def SynthesizeEnumType(domain, owner, type): + type['id'] = ToTitleCase(owner) + ToTitleCase(type['name']) + type_definitions[domain['domain'] + '.' + type['id']] = ( + CreateEnumTypeDefinition(domain['domain'], type)) + type['$ref'] = domain['domain'] + '.' + type['id'] + domain['types'].append(type) + + +def SynthesizeCommandTypes(json_api): + """Generate types for command parameters, return values and enum + properties.""" + for domain in json_api['domains']: + if not 'types' in domain: + domain['types'] = [] + for type in domain['types']: + if type['type'] == 'object': + for property in type.get('properties', []): + if 'enum' in property and not '$ref' in property: + SynthesizeEnumType(domain, type['id'], property) + + for command in domain.get('commands', []): + if 'parameters' in command: + for parameter in command['parameters']: + if 'enum' in parameter and not '$ref' in parameter: + SynthesizeEnumType(domain, command['name'], parameter) + parameters_type = { + 'id': ToTitleCase(command['name']) + 'Params', + 'type': 'object', + 'description': 'Parameters for the %s command.' % ToTitleCase( + command['name']), + 'properties': command['parameters'] + } + domain['types'].append(parameters_type) + if 'returns' in command: + for parameter in command['returns']: + if 'enum' in parameter and not '$ref' in parameter: + SynthesizeEnumType(domain, command['name'], parameter) + result_type = { + 'id': ToTitleCase(command['name']) + 'Result', + 'type': 'object', + 'description': 'Result for the %s command.' % ToTitleCase( + command['name']), + 'properties': command['returns'] + } + domain['types'].append(result_type) + + +def SynthesizeEventTypes(json_api): + """Generate types for events and their properties. + + Note that parameter objects are also created for events without parameters to + make it easier to introduce parameters later. + """ + for domain in json_api['domains']: + if not 'types' in domain: + domain['types'] = [] + for event in domain.get('events', []): + for parameter in event.get('parameters', []): + if 'enum' in parameter and not '$ref' in parameter: + SynthesizeEnumType(domain, event['name'], parameter) + event_type = { + 'id': ToTitleCase(event['name']) + 'Params', + 'type': 'object', + 'description': 'Parameters for the %s event.' % ToTitleCase( + event['name']), + 'properties': event.get('parameters', []) + } + domain['types'].append(event_type) + + +def PatchHiddenCommandsAndEvents(json_api): + """ + Mark all commands and events in hidden domains as hidden and make sure hidden + commands have at least empty parameters and return values. + """ + for domain in json_api['domains']: + if domain.get('hidden', False): + for command in domain.get('commands', []): + command['hidden'] = True + for event in domain.get('events', []): + event['hidden'] = True + for command in domain.get('commands', []): + if not command.get('hidden', False): + continue + if not 'parameters' in command: + command['parameters'] = [] + if not 'returns' in command: + command['returns'] = [] + for event in domain.get('events', []): + if not event.get('hidden', False): + continue + if not 'parameters' in event: + event['parameters'] = [] + + +def Generate(jinja_env, output_dirname, json_api, class_name, file_types): + template_context = { + 'api': json_api, + 'join_arrays': JoinArrays, + 'resolve_type': ResolveType, + 'type_definition': TypeDefinition, + } + for file_type in file_types: + template = jinja_env.get_template('/%s_%s.template' % ( + class_name, file_type)) + output_file = '%s/%s.%s' % (output_dirname, class_name, file_type) + with open(output_file, 'w') as f: + f.write(template.render(template_context)) + + +def GenerateDomains(jinja_env, output_dirname, json_api, class_name, + file_types): + for file_type in file_types: + template = jinja_env.get_template('/%s_%s.template' % ( + class_name, file_type)) + for domain in json_api['domains']: + template_context = { + 'domain': domain, + 'resolve_type': ResolveType, + } + domain_name = CamelCaseToHackerStyle(domain['domain']) + output_file = '%s/%s.%s' % (output_dirname, domain_name, file_type) + with open(output_file, 'w') as f: + f.write(template.render(template_context)) + + +if __name__ == '__main__': + json_api, output_dirname = ParseArguments(sys.argv[1:]) + jinja_env = InitializeJinjaEnv(output_dirname) + PatchHiddenCommandsAndEvents(json_api) + SynthesizeCommandTypes(json_api) + SynthesizeEventTypes(json_api) + PatchFullQualifiedRefs(json_api) + CreateTypeDefinitions(json_api) + Generate(jinja_env, output_dirname, json_api, 'types', ['cc', 'h']) + Generate(jinja_env, output_dirname, json_api, 'type_conversions', ['h']) + GenerateDomains(jinja_env, output_dirname, json_api, 'domain', ['cc', 'h']) diff --git a/chromium/headless/lib/browser/client_api_generator_unittest.py b/chromium/headless/lib/browser/client_api_generator_unittest.py new file mode 100644 index 00000000000..0cd7df3fd37 --- /dev/null +++ b/chromium/headless/lib/browser/client_api_generator_unittest.py @@ -0,0 +1,534 @@ +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import argparse +import client_api_generator +import shutil +import sys +import tempfile +import unittest + + +class ClientApiGeneratorTest(unittest.TestCase): + + def test_ArgumentParsing(self): + with tempfile.NamedTemporaryFile() as f: + f.write('{"foo": true}') + f.flush() + json_api, output_dir = client_api_generator.ParseArguments([ + '--protocol', f.name, '--output_dir', 'out']) + self.assertEqual({'foo': True}, json_api) + self.assertEqual('out', output_dir) + + def test_ToTitleCase(self): + self.assertEqual(client_api_generator.ToTitleCase('fooBar'), 'FooBar') + + def test_DashToCamelCase(self): + self.assertEqual(client_api_generator.DashToCamelCase('foo-bar'), 'FooBar') + self.assertEqual(client_api_generator.DashToCamelCase('foo-'), 'Foo') + self.assertEqual(client_api_generator.DashToCamelCase('-bar'), 'Bar') + + def test_CamelCaseToHackerStyle(self): + self.assertEqual(client_api_generator.CamelCaseToHackerStyle('FooBar'), + 'foo_bar') + self.assertEqual(client_api_generator.CamelCaseToHackerStyle('LoLoLoL'), + 'lo_lo_lol') + + def test_MangleEnum(self): + self.assertEqual(client_api_generator.MangleEnum('FOO'), 'FOO') + self.assertEqual(client_api_generator.MangleEnum('NULL'), 'NONE') + + def test_PatchFullQualifiedRefs(self): + json_api = { + 'domains': [ + { + 'domain': 'domain0', + '$ref': 'reference', + }, + { + 'domain': 'domain1', + '$ref': 'reference', + 'more': [{'$ref': 'domain0.thing'}], + } + ] + } + expected_json_api = { + 'domains': [ + { + 'domain': 'domain0', + '$ref': 'domain0.reference', + }, + { + 'domain': 'domain1', + '$ref': 'domain1.reference', + 'more': [{'$ref': 'domain0.thing'}], + } + ] + } + client_api_generator.PatchFullQualifiedRefs(json_api) + self.assertDictEqual(json_api, expected_json_api) + + def test_NumberType(self): + json_api = { + 'domains': [ + { + 'domain': 'domain', + 'types': [ + { + 'id': 'TestType', + 'type': 'number', + }, + ] + }, + ] + } + client_api_generator.CreateTypeDefinitions(json_api) + type = json_api['domains'][0]['types'][0] + resolved = client_api_generator.ResolveType(type) + self.assertEqual('double', resolved['raw_type']) + + def test_IntegerType(self): + json_api = { + 'domains': [ + { + 'domain': 'domain', + 'types': [ + { + 'id': 'TestType', + 'type': 'integer', + }, + ] + }, + ] + } + client_api_generator.CreateTypeDefinitions(json_api) + type = json_api['domains'][0]['types'][0] + resolved = client_api_generator.ResolveType(type) + self.assertEqual('int', resolved['raw_type']) + + def test_BooleanType(self): + json_api = { + 'domains': [ + { + 'domain': 'domain', + 'types': [ + { + 'id': 'TestType', + 'type': 'boolean', + }, + ] + }, + ] + } + client_api_generator.CreateTypeDefinitions(json_api) + type = json_api['domains'][0]['types'][0] + resolved = client_api_generator.ResolveType(type) + self.assertEqual('bool', resolved['raw_type']) + + def test_StringType(self): + json_api = { + 'domains': [ + { + 'domain': 'domain', + 'types': [ + { + 'id': 'TestType', + 'type': 'string', + }, + ] + }, + ] + } + client_api_generator.CreateTypeDefinitions(json_api) + type = json_api['domains'][0]['types'][0] + resolved = client_api_generator.ResolveType(type) + self.assertEqual('std::string', resolved['raw_type']) + + def test_ObjectType(self): + json_api = { + 'domains': [ + { + 'domain': 'domain', + 'types': [ + { + 'id': 'TestType', + 'type': 'object', + 'properties': [ + {'name': 'p1', 'type': 'number'}, + {'name': 'p2', 'type': 'integer'}, + {'name': 'p3', 'type': 'boolean'}, + {'name': 'p4', 'type': 'string'}, + {'name': 'p5', 'type': 'any'}, + {'name': 'p6', 'type': 'object', '$ref': 'TestType'}, + ], + 'returns': [ + {'name': 'r1', 'type': 'number'}, + {'name': 'r2', 'type': 'integer'}, + {'name': 'r3', 'type': 'boolean'}, + {'name': 'r4', 'type': 'string'}, + {'name': 'r5', 'type': 'any'}, + {'name': 'r6', 'type': 'object', '$ref': 'TestType'}, + ], + }, + ] + }, + ] + } + client_api_generator.CreateTypeDefinitions(json_api) + type = json_api['domains'][0]['types'][0] + resolved = client_api_generator.ResolveType(type) + self.assertEqual('TestType', resolved['raw_type']) + + def test_AnyType(self): + json_api = { + 'domains': [ + { + 'domain': 'domain', + 'types': [ + { + 'id': 'TestType', + 'type': 'any', + }, + ] + }, + ] + } + client_api_generator.CreateTypeDefinitions(json_api) + type = json_api['domains'][0]['types'][0] + resolved = client_api_generator.ResolveType(type) + self.assertEqual('base::Value', resolved['raw_type']) + + def test_ArrayType(self): + json_api = { + 'domains': [ + { + 'domain': 'domain', + 'types': [ + { + 'id': 'TestType', + 'type': 'array', + 'items': {'type': 'integer'} + }, + ] + }, + ] + } + client_api_generator.CreateTypeDefinitions(json_api) + type = json_api['domains'][0]['types'][0] + resolved = client_api_generator.ResolveType(type) + self.assertEqual('std::vector<int>', resolved['raw_type']) + + def test_EnumType(self): + json_api = { + 'domains': [ + { + 'domain': 'domain', + 'types': [ + { + 'id': 'TestType', + 'type': 'string', + 'enum': ['a', 'b', 'c'] + }, + ] + }, + ] + } + client_api_generator.CreateTypeDefinitions(json_api) + type = json_api['domains'][0]['types'][0] + resolved = client_api_generator.ResolveType(type) + self.assertEqual('headless::domain::TestType', resolved['raw_type']) + + def test_SynthesizeCommandTypes(self): + json_api = { + 'domains': [ + { + 'domain': 'domain', + 'commands': [ + { + 'name': 'TestCommand', + 'parameters': [ + {'name': 'p1', 'type': 'number'}, + {'name': 'p2', 'type': 'integer'}, + {'name': 'p3', 'type': 'boolean'}, + {'name': 'p4', 'type': 'string'}, + {'name': 'p5', 'type': 'any'}, + {'name': 'p6', 'type': 'object', '$ref': 'TestType'}, + ], + 'returns': [ + {'name': 'r1', 'type': 'number'}, + {'name': 'r2', 'type': 'integer'}, + {'name': 'r3', 'type': 'boolean'}, + {'name': 'r4', 'type': 'string'}, + {'name': 'r5', 'type': 'any'}, + {'name': 'r6', 'type': 'object', '$ref': 'TestType'}, + ], + }, + ] + }, + ] + } + expected_types = [ + { + 'type': 'object', + 'id': 'TestCommandParams', + 'description': 'Parameters for the TestCommand command.', + 'properties': [ + {'type': 'number', 'name': 'p1'}, + {'type': 'integer', 'name': 'p2'}, + {'type': 'boolean', 'name': 'p3'}, + {'type': 'string', 'name': 'p4'}, + {'type': 'any', 'name': 'p5'}, + {'type': 'object', 'name': 'p6', '$ref': 'TestType'} + ], + }, + { + 'type': 'object', + 'id': 'TestCommandResult', + 'description': 'Result for the TestCommand command.', + 'properties': [ + {'type': 'number', 'name': 'r1'}, + {'type': 'integer', 'name': 'r2'}, + {'type': 'boolean', 'name': 'r3'}, + {'type': 'string', 'name': 'r4'}, + {'type': 'any', 'name': 'r5'}, + {'type': 'object', 'name': 'r6', '$ref': 'TestType'} + ], + } + ] + client_api_generator.SynthesizeCommandTypes(json_api) + types = json_api['domains'][0]['types'] + self.assertListEqual(types, expected_types) + + def test_SynthesizeEventTypes(self): + json_api = { + 'domains': [ + { + 'domain': 'domain', + 'events': [ + { + 'name': 'TestEvent', + 'parameters': [ + {'name': 'p1', 'type': 'number'}, + {'name': 'p2', 'type': 'integer'}, + {'name': 'p3', 'type': 'boolean'}, + {'name': 'p4', 'type': 'string'}, + {'name': 'p5', 'type': 'any'}, + {'name': 'p6', 'type': 'object', '$ref': 'TestType'}, + ] + }, + { + 'name': 'TestEventWithNoParams', + } + ] + } + ] + } + expected_types = [ + { + 'type': 'object', + 'id': 'TestEventParams', + 'description': 'Parameters for the TestEvent event.', + 'properties': [ + {'type': 'number', 'name': 'p1'}, + {'type': 'integer', 'name': 'p2'}, + {'type': 'boolean', 'name': 'p3'}, + {'type': 'string', 'name': 'p4'}, + {'type': 'any', 'name': 'p5'}, + {'type': 'object', 'name': 'p6', '$ref': 'TestType'} + ] + }, + { + 'type': 'object', + 'id': 'TestEventWithNoParamsParams', + 'description': 'Parameters for the TestEventWithNoParams event.', + 'properties': [], + } + ] + client_api_generator.SynthesizeEventTypes(json_api) + types = json_api['domains'][0]['types'] + self.assertListEqual(types, expected_types) + + def test_PatchHiddenDomains(self): + json_api = { + 'domains': [ + { + 'domain': 'domain', + 'hidden': True, + 'commands': [ + { + 'name': 'FooCommand', + } + ], + 'events': [ + { + 'name': 'BarEvent', + } + ] + } + ] + } + expected_types = [ + { + 'type': 'object', + 'id': 'FooCommandParams', + 'description': 'Parameters for the FooCommand command.', + 'properties': [], + }, + { + 'type': 'object', + 'id': 'FooCommandResult', + 'description': 'Result for the FooCommand command.', + 'properties': [], + }, + { + 'type': 'object', + 'id': 'BarEventParams', + 'description': 'Parameters for the BarEvent event.', + 'properties': [], + } + ] + client_api_generator.PatchHiddenCommandsAndEvents(json_api) + client_api_generator.SynthesizeCommandTypes(json_api) + client_api_generator.SynthesizeEventTypes(json_api) + for command in json_api['domains'][0]['commands']: + self.assertTrue(command['hidden']) + for event in json_api['domains'][0]['events']: + self.assertTrue(command['hidden']) + types = json_api['domains'][0]['types'] + self.assertListEqual(types, expected_types) + + def test_PatchHiddenCommandsAndEvents(self): + json_api = { + 'domains': [ + { + 'domain': 'domain', + 'commands': [ + { + 'name': 'FooCommand', + 'hidden': True, + } + ], + 'events': [ + { + 'name': 'BarEvent', + 'hidden': True, + } + ] + } + ] + } + expected_types = [ + { + 'type': 'object', + 'id': 'FooCommandParams', + 'description': 'Parameters for the FooCommand command.', + 'properties': [], + }, + { + 'type': 'object', + 'id': 'FooCommandResult', + 'description': 'Result for the FooCommand command.', + 'properties': [], + }, + { + 'type': 'object', + 'id': 'BarEventParams', + 'description': 'Parameters for the BarEvent event.', + 'properties': [], + } + ] + client_api_generator.PatchHiddenCommandsAndEvents(json_api) + client_api_generator.SynthesizeCommandTypes(json_api) + client_api_generator.SynthesizeEventTypes(json_api) + for command in json_api['domains'][0]['commands']: + self.assertTrue(command['hidden']) + for event in json_api['domains'][0]['events']: + self.assertTrue(command['hidden']) + types = json_api['domains'][0]['types'] + self.assertListEqual(types, expected_types) + + def test_Generate(self): + json_api = { + 'domains': [ + { + 'domain': 'domain', + 'types': [ + { + 'id': 'TestType', + 'type': 'object', + 'properties': [ + {'name': 'p1', 'type': 'number'}, + {'name': 'p2', 'type': 'integer'}, + {'name': 'p3', 'type': 'boolean'}, + {'name': 'p4', 'type': 'string'}, + {'name': 'p5', 'type': 'any'}, + {'name': 'p6', 'type': 'object', '$ref': 'domain.TestType'}, + ], + 'returns': [ + {'name': 'r1', 'type': 'number'}, + {'name': 'r2', 'type': 'integer'}, + {'name': 'r3', 'type': 'boolean'}, + {'name': 'r4', 'type': 'string'}, + {'name': 'r5', 'type': 'any'}, + {'name': 'r6', 'type': 'object', '$ref': 'domain.TestType'}, + ], + }, + ] + }, + ] + } + try: + dirname = tempfile.mkdtemp() + jinja_env = client_api_generator.InitializeJinjaEnv(dirname) + client_api_generator.Generate(jinja_env, dirname, json_api, 'types', + ['cc']) + client_api_generator.Generate(jinja_env, dirname, json_api, 'types', + ['h']) + # This is just a smoke test; we don't actually verify the generated output + # here. + finally: + shutil.rmtree(dirname) + + def test_GenerateDomains(self): + json_api = { + 'domains': [ + { + 'domain': 'domain0', + 'types': [ + { + 'id': 'TestType', + 'type': 'object', + }, + ] + }, + { + 'domain': 'domain1', + 'types': [ + { + 'id': 'TestType', + 'type': 'object', + }, + ] + }, + ] + } + try: + dirname = tempfile.mkdtemp() + jinja_env = client_api_generator.InitializeJinjaEnv(dirname) + client_api_generator.GenerateDomains(jinja_env, dirname, json_api, + 'domain', ['cc', 'h']) + # This is just a smoke test; we don't actually verify the generated output + # here. + finally: + shutil.rmtree(dirname) + + +if __name__ == '__main__': + cmdline_parser = argparse.ArgumentParser() + cmdline_parser.add_argument('--stamp') + args = cmdline_parser.parse_args() + unittest.main(verbosity=2, exit=False, argv=sys.argv[:1]) + if args.stamp: + with open(args.stamp, 'a') as f: + pass diff --git a/chromium/headless/lib/browser/domain_cc.template b/chromium/headless/lib/browser/domain_cc.template new file mode 100644 index 00000000000..118d996627e --- /dev/null +++ b/chromium/headless/lib/browser/domain_cc.template @@ -0,0 +1,142 @@ +// This file is generated + +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "headless/public/domains/{{domain.domain | camelcase_to_hacker_style}}.h" + +#include "base/bind.h" + +namespace headless { + +namespace {{domain.domain | camelcase_to_hacker_style}} { + +ExperimentalDomain* Domain::GetExperimental() { + return static_cast<ExperimentalDomain*>(this); +} + + {% if "events" in domain %} +void Domain::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void Domain::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + {% endif %} + + {% for command in domain.commands %} + {# Skip redirected commands. #} + {% if "redirect" in command %}{% continue %}{% endif %} + + {% set class_name = 'ExperimentalDomain' if command.hidden else 'Domain' %} + {% set method_name = command.name | to_title_case %} + {% if "parameters" in command and "returns" in command %} +void {{class_name}}::{{method_name}}(std::unique_ptr<{{method_name}}Params> params, base::Callback<void(std::unique_ptr<{{method_name}}Result>)> callback) { + dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", params->Serialize(), base::Bind(&Domain::Handle{{method_name}}Response, callback)); + {% elif "parameters" in command %} +void {{class_name}}::{{method_name}}(std::unique_ptr<{{method_name}}Params> params, base::Callback<void()> callback) { + dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", params->Serialize(), std::move(callback)); + {% elif "returns" in command %} +void {{class_name}}::{{method_name}}(base::Callback<void(std::unique_ptr<{{method_name}}Result>)> callback) { + dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", base::Bind(&Domain::Handle{{method_name}}Response, std::move(callback))); + {% else %} +void {{class_name}}::{{method_name}}(base::Callback<void()> callback) { + dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", std::move(callback)); + {% endif %} +} + {# Generate convenience methods that take the required parameters directly. #} + {% if not "parameters" in command %}{% continue %}{% endif %} + {# Don't generate these for hidden commands. #} + {% if command.hidden %}{% continue %}{% endif %} + +void {{class_name}}::{{method_name}}({##} + {% for parameter in command.parameters -%} + {% if parameter.get("optional", False) -%} + {% break %} + {% endif %} + {% if not loop.first %}, {% endif %} +{{resolve_type(parameter).pass_type}} {{parameter.name | camelcase_to_hacker_style -}} + {% endfor %} + {% if "parameters" in command and not command.parameters[0].get("optional", False) %}, {% endif %}{# -#} + {% if "returns" in command -%} + base::Callback<void(std::unique_ptr<{{method_name}}Result>)> callback{##} + {% else -%} + base::Callback<void()> callback{##} + {% endif %}) { + {# Build the parameters object. #} + std::unique_ptr<{{method_name}}Params> params = {{method_name}}Params::Builder() + {% for parameter in command.parameters -%} + {% if parameter.get("optional", False) -%} + {% break %} + {% endif %} + .Set{{parameter.name | to_title_case}}(std::move({{parameter.name | camelcase_to_hacker_style}})) + {% endfor %} + .Build(); + {# Send the message. #} + {{method_name}}(std::move(params), std::move(callback)); +} + {% endfor %} + +{# Generate response handlers for commands that need them. #} +{% for command in domain.commands %} + {% if not "returns" in command %}{% continue %}{% endif %} + {% set method_name = command.name | to_title_case %} + +// static +void Domain::Handle{{method_name}}Response(base::Callback<void(std::unique_ptr<{{method_name}}Result>)> callback, const base::Value& response) { + if (callback.is_null()) + return; + ErrorReporter errors; + std::unique_ptr<{{method_name}}Result> result = {{method_name}}Result::Parse(response, &errors); + DCHECK(!errors.HasErrors()); + callback.Run(std::move(result)); +} +{% endfor %} +{% if "events" in domain %} + {% for event in domain.events %} + +{# Generate dispatchers which call registered observers for an event. #} +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()); + FOR_EACH_OBSERVER(ExperimentalObserver, observers_, On{{event.name | to_title_case}}(*parsed_params)); +} + {% endfor %} +{% endif %} + +Domain::Domain(internal::MessageDispatcher* dispatcher) + : dispatcher_(dispatcher) { + {% if "events" in domain %} + {# Register all events in this domain. #} + {% for event in domain.events %} + dispatcher_->RegisterEventHandler( + "{{domain.domain}}.{{event.name}}", + base::Bind(&Domain::Dispatch{{event.name | to_title_case}}Event, + base::Unretained(this))); + {% endfor %} + {% endif %} +} + +Domain::~Domain() {} + +ExperimentalDomain::ExperimentalDomain(internal::MessageDispatcher* dispatcher) + : Domain(dispatcher) {} + +ExperimentalDomain::~ExperimentalDomain() {} + + {% if "events" in domain %} +void ExperimentalDomain::AddObserver(ExperimentalObserver* observer) { + observers_.AddObserver(observer); +} + +void ExperimentalDomain::RemoveObserver(ExperimentalObserver* observer) { + observers_.RemoveObserver(observer); +} + {% endif %} + +} // namespace {{domain.domain | camelcase_to_hacker_style}} + +} // namespace headless diff --git a/chromium/headless/lib/browser/domain_h.template b/chromium/headless/lib/browser/domain_h.template new file mode 100644 index 00000000000..607b5be16f2 --- /dev/null +++ b/chromium/headless/lib/browser/domain_h.template @@ -0,0 +1,158 @@ +// This file is generated + +// Copyright (c) 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef HEADLESS_PUBLIC_DOMAINS_{{domain.domain | camelcase_to_hacker_style | upper}}_H_ +#define HEADLESS_PUBLIC_DOMAINS_{{domain.domain | camelcase_to_hacker_style | upper}}_H_ + +#include "base/callback.h" +#include "base/observer_list.h" +#include "base/values.h" +#include "headless/public/domains/types.h" +#include "headless/public/headless_export.h" +#include "headless/public/internal/message_dispatcher.h" + +{# Macro for defining a member function for a given command. #} +{% macro command_decl(command) %} + {% set method_name = command.name | to_title_case %} + {% if command.description %} + // {{ command.description }} + {% endif %} + {% if "parameters" in command and "returns" in command %} + void {{method_name}}(std::unique_ptr<{{method_name}}Params> params, base::Callback<void(std::unique_ptr<{{method_name}}Result>)> callback = base::Callback<void(std::unique_ptr<{{method_name}}Result>)>()); + {% elif "parameters" in command %} + void {{method_name}}(std::unique_ptr<{{method_name}}Params> params, base::Callback<void()> callback = base::Callback<void()>()); + {% elif "returns" in command %} + void {{method_name}}(base::Callback<void(std::unique_ptr<{{method_name}}Result>)> callback = base::Callback<void(std::unique_ptr<{{method_name}}Result>)>()); + {% else %} + void {{method_name}}(base::Callback<void()> callback = base::Callback<void()>()); + {% endif %} + {# Generate convenience methods that take the required parameters directly. #} + {# Don't generate these for hidden commands. #} + {% if "parameters" in command and not command.hidden %} + void {{method_name}}({##} + {% for parameter in command.parameters -%} + {% if parameter.get("optional", False) -%} + {% break %} + {% endif %} + {% if not loop.first %}, {% endif %} +{{resolve_type(parameter).pass_type}} {{parameter.name | camelcase_to_hacker_style -}} + {% endfor %} + {% if "parameters" in command and not command.parameters[0].get("optional", False) %}, {% endif %}{# -#} + {% if "returns" in command -%} + base::Callback<void(std::unique_ptr<{{method_name}}Result>)> callback = base::Callback<void(std::unique_ptr<{{method_name}}Result>)>(){##} + {% else -%} + base::Callback<void()> callback = base::Callback<void()>(){##} + {% endif %}); + {% endif %} +{% endmacro %} + +namespace headless { +namespace {{domain.domain | camelcase_to_hacker_style}} { +class ExperimentalDomain; +class ExperimentalObserver; +{% if "events" in domain %} + +class HEADLESS_EXPORT ExperimentalObserver { + public: + virtual ~ExperimentalObserver() {} + {% for event in domain.events %} + {% if event.description %} + // {{event.description}} + {% endif %} + virtual void On{{event.name | to_title_case}}(const {{event.name | to_title_case}}Params& params) {} + {% endfor %} +}; + +class HEADLESS_EXPORT Observer : public ExperimentalObserver { + public: + virtual ~Observer() {} + {% for event in domain.events %} + {% if event.description %} + // {% if event.hidden %}Experimental: {% endif %}{{event.description}} + {% endif %} + virtual void On{{event.name | to_title_case}}(const {{event.name | to_title_case}}Params& params) {% if event.hidden %}final {% endif %}{} + {% endfor %} +}; +{% endif %} + +{% if domain.description %} +// {{domain.description}} +{% endif %} +class HEADLESS_EXPORT Domain { + public: + {% if "events" in domain %} + // Add or remove an observer. |observer| must be removed before being + // destroyed. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + {% endif %} + + // Return the experimental interface for this domain. Note that experimental + // commands may be changed or removed at any time. + ExperimentalDomain* GetExperimental(); + + {# Generate methods for each command. #} + {% for command in domain.commands %} + {# Skip redirected commands. #} + {% if "redirect" in command %}{% continue %}{% endif %} + {# Skip hidden commands. #} + {% if command.hidden %}{% continue %}{% endif %} +{{ command_decl(command) }} + {% endfor %} + protected: + Domain(internal::MessageDispatcher* dispatcher); + ~Domain(); + + {# Generate response handlers for commands that need them. #} + {% for command in domain.commands %} + {% if not "returns" in command %}{% continue %}{% endif %} + {% set method_name = command.name | to_title_case %} + static void Handle{{method_name}}Response(base::Callback<void(std::unique_ptr<{{method_name}}Result>)> callback, const base::Value& response); + {% endfor %} + + {# Generate event dispatchers. #} + {% for event in domain.events %} + void Dispatch{{event.name | to_title_case}}Event(const base::Value& params); + {% endfor %} + + internal::MessageDispatcher* dispatcher_; // Not owned. + {% if "events" in domain %} + base::ObserverList<ExperimentalObserver> observers_; + {% endif %} + + private: + DISALLOW_COPY_AND_ASSIGN(Domain); +}; + +class ExperimentalDomain : public Domain { + public: + ExperimentalDomain(internal::MessageDispatcher* dispatcher); + ~ExperimentalDomain(); + + {% if "events" in domain %} + // Add or remove an observer. |observer| must be removed before being + // destroyed. + void AddObserver(ExperimentalObserver* observer); + void RemoveObserver(ExperimentalObserver* observer); + {% endif %} + + {# Generate methods for each experimental command. #} + {% for command in domain.commands %} + {# Skip redirected commands. #} + {% if "redirect" in command %}{% continue %}{% endif %} + {# Skip non-hidden commands. #} + {% if not command.hidden %}{% continue %}{% endif %} +{{ command_decl(command) }} + {% endfor %} + + private: + DISALLOW_COPY_AND_ASSIGN(ExperimentalDomain); +}; + +} // namespace {{domain.domain | camelcase_to_hacker_style}} +} // namespace headless + +#endif // HEADLESS_PUBLIC_DOMAINS_{{domain.domain | camelcase_to_hacker_style | upper}}_H_ diff --git a/chromium/headless/lib/browser/headless_browser_context.cc b/chromium/headless/lib/browser/headless_browser_context.cc index b48f09012e2..59e50ae2025 100644 --- a/chromium/headless/lib/browser/headless_browser_context.cc +++ b/chromium/headless/lib/browser/headless_browser_context.cc @@ -36,6 +36,10 @@ class HeadlessResourceContext : public content::ResourceContext { url_request_context_getter_ = std::move(url_request_context_getter); } + net::URLRequestContextGetter* url_request_context_getter() { + return url_request_context_getter_.get(); + } + private: scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_; @@ -93,27 +97,6 @@ bool HeadlessBrowserContext::IsOffTheRecord() const { return false; } -net::URLRequestContextGetter* HeadlessBrowserContext::GetRequestContext() { - return GetDefaultStoragePartition(this)->GetURLRequestContext(); -} - -net::URLRequestContextGetter* HeadlessBrowserContext::GetMediaRequestContext() { - return GetRequestContext(); -} - -net::URLRequestContextGetter* -HeadlessBrowserContext::GetMediaRequestContextForRenderProcess( - int renderer_child_id) { - return GetRequestContext(); -} - -net::URLRequestContextGetter* -HeadlessBrowserContext::GetMediaRequestContextForStoragePartition( - const base::FilePath& partition_path, - bool in_memory) { - return GetRequestContext(); -} - content::ResourceContext* HeadlessBrowserContext::GetResourceContext() { return resource_context_.get(); } @@ -177,6 +160,18 @@ HeadlessBrowserContext::CreateRequestContextForStoragePartition( return nullptr; } +net::URLRequestContextGetter* +HeadlessBrowserContext::CreateMediaRequestContext() { + return resource_context_->url_request_context_getter(); +} + +net::URLRequestContextGetter* +HeadlessBrowserContext::CreateMediaRequestContextForStoragePartition( + const base::FilePath& partition_path, + bool in_memory) { + return nullptr; +} + void HeadlessBrowserContext::SetOptionsForTesting( const HeadlessBrowser::Options& options) { options_ = options; diff --git a/chromium/headless/lib/browser/headless_browser_context.h b/chromium/headless/lib/browser/headless_browser_context.h index 6fd6676de16..865b3c7f977 100644 --- a/chromium/headless/lib/browser/headless_browser_context.h +++ b/chromium/headless/lib/browser/headless_browser_context.h @@ -27,13 +27,6 @@ class HeadlessBrowserContext : public content::BrowserContext { const base::FilePath& partition_path) override; base::FilePath GetPath() const override; bool IsOffTheRecord() const override; - net::URLRequestContextGetter* GetRequestContext() override; - net::URLRequestContextGetter* GetMediaRequestContext() override; - net::URLRequestContextGetter* GetMediaRequestContextForRenderProcess( - int renderer_child_id) override; - net::URLRequestContextGetter* GetMediaRequestContextForStoragePartition( - const base::FilePath& partition_path, - bool in_memory) override; content::ResourceContext* GetResourceContext() override; content::DownloadManagerDelegate* GetDownloadManagerDelegate() override; content::BrowserPluginGuestManager* GetGuestManager() override; @@ -50,6 +43,10 @@ class HeadlessBrowserContext : public content::BrowserContext { bool in_memory, content::ProtocolHandlerMap* protocol_handlers, content::URLRequestInterceptorScopedVector request_interceptors) override; + net::URLRequestContextGetter* CreateMediaRequestContext() override; + net::URLRequestContextGetter* CreateMediaRequestContextForStoragePartition( + const base::FilePath& partition_path, + bool in_memory) override; const HeadlessBrowser::Options& options() const { return options_; } void SetOptionsForTesting(const HeadlessBrowser::Options& options); diff --git a/chromium/headless/lib/browser/headless_browser_impl.cc b/chromium/headless/lib/browser/headless_browser_impl.cc index 57564d7ddc8..06b3f14ad80 100644 --- a/chromium/headless/lib/browser/headless_browser_impl.cc +++ b/chromium/headless/lib/browser/headless_browser_impl.cc @@ -5,13 +5,14 @@ #include "headless/lib/browser/headless_browser_impl.h" #include "base/memory/ptr_util.h" -#include "base/thread_task_runner_handle.h" +#include "base/threading/thread_task_runner_handle.h" #include "content/public/app/content_main.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/web_contents.h" #include "headless/lib/browser/headless_browser_context.h" #include "headless/lib/browser/headless_browser_main_parts.h" #include "headless/lib/browser/headless_web_contents_impl.h" +#include "headless/lib/browser/headless_window_tree_client.h" #include "headless/lib/headless_content_main_delegate.h" #include "ui/aura/env.h" #include "ui/aura/window_tree_host.h" @@ -30,19 +31,18 @@ HeadlessBrowserImpl::HeadlessBrowserImpl( HeadlessBrowserImpl::~HeadlessBrowserImpl() {} -std::unique_ptr<HeadlessWebContents> HeadlessBrowserImpl::CreateWebContents( +HeadlessWebContents* HeadlessBrowserImpl::CreateWebContents( const GURL& initial_url, const gfx::Size& size) { DCHECK(BrowserMainThread()->BelongsToCurrentThread()); - std::unique_ptr<HeadlessWebContentsImpl> web_contents = - base::WrapUnique(new HeadlessWebContentsImpl( - browser_context(), window_tree_host_->window(), size)); - // We require the user to pass in an initial URL to ensure that the renderer - // gets initialized and eventually becomes ready to be inspected. See - // HeadlessWebContents::Observer::WebContentsReady. - if (!web_contents->OpenURL(initial_url)) + std::unique_ptr<HeadlessWebContentsImpl> headless_web_contents = + HeadlessWebContentsImpl::Create(browser_context(), + window_tree_host_->window(), size, this); + + if (!headless_web_contents->OpenURL(initial_url)) return nullptr; - return std::move(web_contents); + + return RegisterWebContents(std::move(headless_web_contents)); } scoped_refptr<base::SingleThreadTaskRunner> @@ -57,6 +57,17 @@ void HeadlessBrowserImpl::Shutdown() { base::MessageLoop::QuitWhenIdleClosure()); } +std::vector<HeadlessWebContents*> HeadlessBrowserImpl::GetAllWebContents() { + std::vector<HeadlessWebContents*> result; + result.reserve(web_contents_.size()); + + for (const auto& web_contents_pair : web_contents_) { + result.push_back(web_contents_pair.first); + } + + return result; +} + HeadlessBrowserContext* HeadlessBrowserImpl::browser_context() const { DCHECK(BrowserMainThread()->BelongsToCurrentThread()); DCHECK(browser_main_parts()); @@ -79,10 +90,27 @@ void HeadlessBrowserImpl::RunOnStartCallback() { window_tree_host_.reset(aura::WindowTreeHost::Create(gfx::Rect())); window_tree_host_->InitHost(); + window_tree_client_.reset( + new HeadlessWindowTreeClient(window_tree_host_->window())); + on_start_callback_.Run(this); on_start_callback_ = base::Callback<void(HeadlessBrowser*)>(); } +HeadlessWebContentsImpl* HeadlessBrowserImpl::RegisterWebContents( + std::unique_ptr<HeadlessWebContentsImpl> web_contents) { + HeadlessWebContentsImpl* unowned_web_contents = web_contents.get(); + web_contents_[unowned_web_contents] = std::move(web_contents); + return unowned_web_contents; +} + +void HeadlessBrowserImpl::DestroyWebContents( + HeadlessWebContentsImpl* web_contents) { + auto it = web_contents_.find(web_contents); + DCHECK(it != web_contents_.end()); + web_contents_.erase(it); +} + void HeadlessBrowserImpl::SetOptionsForTesting( const HeadlessBrowser::Options& options) { options_ = options; diff --git a/chromium/headless/lib/browser/headless_browser_impl.h b/chromium/headless/lib/browser/headless_browser_impl.h index eadeb56b64d..53bcf1385ac 100644 --- a/chromium/headless/lib/browser/headless_browser_impl.h +++ b/chromium/headless/lib/browser/headless_browser_impl.h @@ -8,12 +8,17 @@ #include "headless/public/headless_browser.h" #include <memory> +#include <unordered_map> #include "base/synchronization/lock.h" #include "headless/lib/browser/headless_web_contents_impl.h" namespace aura { class WindowTreeHost; + +namespace client { +class WindowTreeClient; +} } namespace headless { @@ -29,14 +34,15 @@ class HeadlessBrowserImpl : public HeadlessBrowser { ~HeadlessBrowserImpl() override; // HeadlessBrowser implementation: - std::unique_ptr<HeadlessWebContents> CreateWebContents( - const GURL& initial_url, - const gfx::Size& size) override; + HeadlessWebContents* CreateWebContents(const GURL& initial_url, + const gfx::Size& size) override; scoped_refptr<base::SingleThreadTaskRunner> BrowserMainThread() const override; void Shutdown() override; + std::vector<HeadlessWebContents*> GetAllWebContents() override; + void set_browser_main_parts(HeadlessBrowserMainParts* browser_main_parts); HeadlessBrowserMainParts* browser_main_parts() const; @@ -46,6 +52,12 @@ class HeadlessBrowserImpl : public HeadlessBrowser { const HeadlessBrowser::Options& options() const { return options_; } + HeadlessWebContentsImpl* RegisterWebContents( + std::unique_ptr<HeadlessWebContentsImpl> web_contents); + + // Close given |web_contents| and delete it. + void DestroyWebContents(HeadlessWebContentsImpl* web_contents); + // Customize the options used by this headless browser instance. Note that // options which take effect before the message loop has been started (e.g., // custom message pumps) cannot be set via this method. @@ -56,6 +68,10 @@ class HeadlessBrowserImpl : public HeadlessBrowser { HeadlessBrowser::Options options_; HeadlessBrowserMainParts* browser_main_parts_; // Not owned. std::unique_ptr<aura::WindowTreeHost> window_tree_host_; + std::unique_ptr<aura::client::WindowTreeClient> window_tree_client_; + + std::unordered_map<HeadlessWebContents*, std::unique_ptr<HeadlessWebContents>> + web_contents_; DISALLOW_COPY_AND_ASSIGN(HeadlessBrowserImpl); }; diff --git a/chromium/headless/lib/browser/headless_browser_main_parts.cc b/chromium/headless/lib/browser/headless_browser_main_parts.cc index c85ba353244..7a2ed47f028 100644 --- a/chromium/headless/lib/browser/headless_browser_main_parts.cc +++ b/chromium/headless/lib/browser/headless_browser_main_parts.cc @@ -10,7 +10,7 @@ #include "headless/lib/browser/headless_devtools.h" #include "headless/lib/browser/headless_screen.h" #include "ui/aura/env.h" -#include "ui/gfx/screen.h" +#include "ui/display/screen.h" namespace headless { @@ -18,11 +18,10 @@ namespace { void PlatformInitialize() { HeadlessScreen* screen = HeadlessScreen::Create(gfx::Size()); - gfx::Screen::SetScreenInstance(screen); + display::Screen::SetScreenInstance(screen); } void PlatformExit() { - aura::Env::DeleteInstance(); } } // namespace diff --git a/chromium/headless/lib/browser/headless_devtools_client_impl.cc b/chromium/headless/lib/browser/headless_devtools_client_impl.cc new file mode 100644 index 00000000000..2f662bb6695 --- /dev/null +++ b/chromium/headless/lib/browser/headless_devtools_client_impl.cc @@ -0,0 +1,346 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "headless/lib/browser/headless_devtools_client_impl.h" + +#include <memory> + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/memory/ptr_util.h" +#include "base/values.h" +#include "content/public/browser/devtools_agent_host.h" + +namespace headless { + +// static +std::unique_ptr<HeadlessDevToolsClient> HeadlessDevToolsClient::Create() { + return base::WrapUnique(new HeadlessDevToolsClientImpl()); +} + +// static +HeadlessDevToolsClientImpl* HeadlessDevToolsClientImpl::From( + HeadlessDevToolsClient* client) { + // This downcast is safe because there is only one implementation of + // HeadlessDevToolsClient. + return static_cast<HeadlessDevToolsClientImpl*>(client); +} + +HeadlessDevToolsClientImpl::HeadlessDevToolsClientImpl() + : agent_host_(nullptr), + next_message_id_(0), + accessibility_domain_(this), + animation_domain_(this), + application_cache_domain_(this), + cache_storage_domain_(this), + console_domain_(this), + css_domain_(this), + database_domain_(this), + debugger_domain_(this), + device_orientation_domain_(this), + dom_debugger_domain_(this), + dom_domain_(this), + dom_storage_domain_(this), + emulation_domain_(this), + heap_profiler_domain_(this), + indexeddb_domain_(this), + input_domain_(this), + inspector_domain_(this), + io_domain_(this), + layer_tree_domain_(this), + memory_domain_(this), + network_domain_(this), + page_domain_(this), + profiler_domain_(this), + rendering_domain_(this), + runtime_domain_(this), + security_domain_(this), + service_worker_domain_(this), + tracing_domain_(this), + worker_domain_(this) {} + +HeadlessDevToolsClientImpl::~HeadlessDevToolsClientImpl() {} + +void HeadlessDevToolsClientImpl::AttachToHost( + content::DevToolsAgentHost* agent_host) { + DCHECK(!agent_host_); + agent_host_ = agent_host; + agent_host_->AttachClient(this); +} + +void HeadlessDevToolsClientImpl::DetachFromHost( + content::DevToolsAgentHost* agent_host) { + DCHECK_EQ(agent_host_, agent_host); + agent_host_->DetachClient(); + agent_host_ = nullptr; + pending_messages_.clear(); +} + +void HeadlessDevToolsClientImpl::DispatchProtocolMessage( + content::DevToolsAgentHost* agent_host, + const std::string& json_message) { + DCHECK_EQ(agent_host_, agent_host); + std::unique_ptr<base::Value> message = + base::JSONReader::Read(json_message, base::JSON_PARSE_RFC); + const base::DictionaryValue* message_dict; + if (!message || !message->GetAsDictionary(&message_dict)) { + NOTREACHED() << "Badly formed reply"; + return; + } + if (!DispatchMessageReply(*message_dict) && !DispatchEvent(*message_dict)) + DLOG(ERROR) << "Unhandled protocol message: " << json_message; +} + +bool HeadlessDevToolsClientImpl::DispatchMessageReply( + const base::DictionaryValue& message_dict) { + int id = 0; + if (!message_dict.GetInteger("id", &id)) + return false; + auto it = pending_messages_.find(id); + if (it == pending_messages_.end()) { + NOTREACHED() << "Unexpected reply"; + return false; + } + Callback callback = std::move(it->second); + pending_messages_.erase(it); + if (!callback.callback_with_result.is_null()) { + const base::DictionaryValue* result_dict; + if (!message_dict.GetDictionary("result", &result_dict)) { + NOTREACHED() << "Badly formed reply result"; + return false; + } + callback.callback_with_result.Run(*result_dict); + } else if (!callback.callback.is_null()) { + callback.callback.Run(); + } + return true; +} + +bool HeadlessDevToolsClientImpl::DispatchEvent( + const base::DictionaryValue& message_dict) { + std::string method; + if (!message_dict.GetString("method", &method)) + return false; + auto it = event_handlers_.find(method); + if (it == event_handlers_.end()) { + NOTREACHED() << "Unknown event: " << method; + return false; + } + if (!it->second.is_null()) { + const base::DictionaryValue* result_dict; + if (!message_dict.GetDictionary("params", &result_dict)) { + NOTREACHED() << "Badly formed event parameters"; + return false; + } + it->second.Run(*result_dict); + } + return true; +} + +void HeadlessDevToolsClientImpl::AgentHostClosed( + content::DevToolsAgentHost* agent_host, + bool replaced_with_another_client) { + DCHECK_EQ(agent_host_, agent_host); + agent_host = nullptr; + pending_messages_.clear(); +} + +accessibility::Domain* HeadlessDevToolsClientImpl::GetAccessibility() { + return &accessibility_domain_; +} + +animation::Domain* HeadlessDevToolsClientImpl::GetAnimation() { + return &animation_domain_; +} + +application_cache::Domain* HeadlessDevToolsClientImpl::GetApplicationCache() { + return &application_cache_domain_; +} + +cache_storage::Domain* HeadlessDevToolsClientImpl::GetCacheStorage() { + return &cache_storage_domain_; +} + +console::Domain* HeadlessDevToolsClientImpl::GetConsole() { + return &console_domain_; +} + +css::Domain* HeadlessDevToolsClientImpl::GetCSS() { + return &css_domain_; +} + +database::Domain* HeadlessDevToolsClientImpl::GetDatabase() { + return &database_domain_; +} + +debugger::Domain* HeadlessDevToolsClientImpl::GetDebugger() { + return &debugger_domain_; +} + +device_orientation::Domain* HeadlessDevToolsClientImpl::GetDeviceOrientation() { + return &device_orientation_domain_; +} + +dom_debugger::Domain* HeadlessDevToolsClientImpl::GetDOMDebugger() { + return &dom_debugger_domain_; +} + +dom::Domain* HeadlessDevToolsClientImpl::GetDOM() { + return &dom_domain_; +} + +dom_storage::Domain* HeadlessDevToolsClientImpl::GetDOMStorage() { + return &dom_storage_domain_; +} + +emulation::Domain* HeadlessDevToolsClientImpl::GetEmulation() { + return &emulation_domain_; +} + +heap_profiler::Domain* HeadlessDevToolsClientImpl::GetHeapProfiler() { + return &heap_profiler_domain_; +} + +indexeddb::Domain* HeadlessDevToolsClientImpl::GetIndexedDB() { + return &indexeddb_domain_; +} + +input::Domain* HeadlessDevToolsClientImpl::GetInput() { + return &input_domain_; +} + +inspector::Domain* HeadlessDevToolsClientImpl::GetInspector() { + return &inspector_domain_; +} + +io::Domain* HeadlessDevToolsClientImpl::GetIO() { + return &io_domain_; +} + +layer_tree::Domain* HeadlessDevToolsClientImpl::GetLayerTree() { + return &layer_tree_domain_; +} + +memory::Domain* HeadlessDevToolsClientImpl::GetMemory() { + return &memory_domain_; +} + +network::Domain* HeadlessDevToolsClientImpl::GetNetwork() { + return &network_domain_; +} + +page::Domain* HeadlessDevToolsClientImpl::GetPage() { + return &page_domain_; +} + +profiler::Domain* HeadlessDevToolsClientImpl::GetProfiler() { + return &profiler_domain_; +} + +rendering::Domain* HeadlessDevToolsClientImpl::GetRendering() { + return &rendering_domain_; +} + +runtime::Domain* HeadlessDevToolsClientImpl::GetRuntime() { + return &runtime_domain_; +} + +security::Domain* HeadlessDevToolsClientImpl::GetSecurity() { + return &security_domain_; +} + +service_worker::Domain* HeadlessDevToolsClientImpl::GetServiceWorker() { + return &service_worker_domain_; +} + +tracing::Domain* HeadlessDevToolsClientImpl::GetTracing() { + return &tracing_domain_; +} + +worker::Domain* HeadlessDevToolsClientImpl::GetWorker() { + return &worker_domain_; +} + +template <typename CallbackType> +void HeadlessDevToolsClientImpl::FinalizeAndSendMessage( + base::DictionaryValue* message, + CallbackType callback) { + DCHECK(agent_host_); + int id = next_message_id_++; + message->SetInteger("id", id); + std::string json_message; + base::JSONWriter::Write(*message, &json_message); + pending_messages_[id] = Callback(callback); + agent_host_->DispatchProtocolMessage(json_message); +} + +template <typename CallbackType> +void HeadlessDevToolsClientImpl::SendMessageWithParams( + const char* method, + std::unique_ptr<base::Value> params, + CallbackType callback) { + base::DictionaryValue message; + message.SetString("method", method); + message.Set("params", std::move(params)); + FinalizeAndSendMessage(&message, std::move(callback)); +} + +template <typename CallbackType> +void HeadlessDevToolsClientImpl::SendMessageWithoutParams( + const char* method, + CallbackType callback) { + base::DictionaryValue message; + message.SetString("method", method); + FinalizeAndSendMessage(&message, std::move(callback)); +} + +void HeadlessDevToolsClientImpl::SendMessage( + const char* method, + std::unique_ptr<base::Value> params, + base::Callback<void(const base::Value&)> callback) { + SendMessageWithParams(method, std::move(params), std::move(callback)); +} + +void HeadlessDevToolsClientImpl::SendMessage( + const char* method, + std::unique_ptr<base::Value> params, + base::Callback<void()> callback) { + SendMessageWithParams(method, std::move(params), std::move(callback)); +} + +void HeadlessDevToolsClientImpl::SendMessage( + const char* method, + base::Callback<void(const base::Value&)> callback) { + SendMessageWithoutParams(method, std::move(callback)); +} + +void HeadlessDevToolsClientImpl::SendMessage(const char* method, + base::Callback<void()> callback) { + SendMessageWithoutParams(method, std::move(callback)); +} + +void HeadlessDevToolsClientImpl::RegisterEventHandler( + const char* method, + base::Callback<void(const base::Value&)> callback) { + DCHECK(event_handlers_.find(method) == event_handlers_.end()); + event_handlers_[method] = callback; +} + +HeadlessDevToolsClientImpl::Callback::Callback() {} + +HeadlessDevToolsClientImpl::Callback::Callback(Callback&& other) = default; + +HeadlessDevToolsClientImpl::Callback::Callback(base::Callback<void()> callback) + : callback(callback) {} + +HeadlessDevToolsClientImpl::Callback::Callback( + base::Callback<void(const base::Value&)> callback) + : callback_with_result(callback) {} + +HeadlessDevToolsClientImpl::Callback::~Callback() {} + +HeadlessDevToolsClientImpl::Callback& HeadlessDevToolsClientImpl::Callback:: +operator=(Callback&& other) = default; + +} // namespace headless diff --git a/chromium/headless/lib/browser/headless_devtools_client_impl.h b/chromium/headless/lib/browser/headless_devtools_client_impl.h new file mode 100644 index 00000000000..c7dbb52db45 --- /dev/null +++ b/chromium/headless/lib/browser/headless_devtools_client_impl.h @@ -0,0 +1,189 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef HEADLESS_LIB_BROWSER_HEADLESS_DEVTOOLS_CLIENT_IMPL_H_ +#define HEADLESS_LIB_BROWSER_HEADLESS_DEVTOOLS_CLIENT_IMPL_H_ + +#include <unordered_map> + +#include "content/public/browser/devtools_agent_host_client.h" +#include "headless/public/domains/accessibility.h" +#include "headless/public/domains/animation.h" +#include "headless/public/domains/application_cache.h" +#include "headless/public/domains/cache_storage.h" +#include "headless/public/domains/console.h" +#include "headless/public/domains/css.h" +#include "headless/public/domains/database.h" +#include "headless/public/domains/debugger.h" +#include "headless/public/domains/device_orientation.h" +#include "headless/public/domains/dom.h" +#include "headless/public/domains/dom_debugger.h" +#include "headless/public/domains/dom_storage.h" +#include "headless/public/domains/emulation.h" +#include "headless/public/domains/heap_profiler.h" +#include "headless/public/domains/indexeddb.h" +#include "headless/public/domains/input.h" +#include "headless/public/domains/inspector.h" +#include "headless/public/domains/io.h" +#include "headless/public/domains/layer_tree.h" +#include "headless/public/domains/memory.h" +#include "headless/public/domains/network.h" +#include "headless/public/domains/page.h" +#include "headless/public/domains/profiler.h" +#include "headless/public/domains/rendering.h" +#include "headless/public/domains/runtime.h" +#include "headless/public/domains/security.h" +#include "headless/public/domains/service_worker.h" +#include "headless/public/domains/tracing.h" +#include "headless/public/domains/worker.h" +#include "headless/public/headless_devtools_client.h" +#include "headless/public/internal/message_dispatcher.h" + +namespace base { +class DictionaryValue; +} + +namespace content { +class DevToolsAgentHost; +} + +namespace headless { + +class HeadlessDevToolsClientImpl : public HeadlessDevToolsClient, + public content::DevToolsAgentHostClient, + public internal::MessageDispatcher { + public: + HeadlessDevToolsClientImpl(); + ~HeadlessDevToolsClientImpl() override; + + static HeadlessDevToolsClientImpl* From(HeadlessDevToolsClient* client); + + // HeadlessDevToolsClient implementation: + accessibility::Domain* GetAccessibility() override; + animation::Domain* GetAnimation() override; + application_cache::Domain* GetApplicationCache() override; + cache_storage::Domain* GetCacheStorage() override; + console::Domain* GetConsole() override; + css::Domain* GetCSS() override; + database::Domain* GetDatabase() override; + debugger::Domain* GetDebugger() override; + device_orientation::Domain* GetDeviceOrientation() override; + dom::Domain* GetDOM() override; + dom_debugger::Domain* GetDOMDebugger() override; + dom_storage::Domain* GetDOMStorage() override; + emulation::Domain* GetEmulation() override; + heap_profiler::Domain* GetHeapProfiler() override; + indexeddb::Domain* GetIndexedDB() override; + input::Domain* GetInput() override; + inspector::Domain* GetInspector() override; + io::Domain* GetIO() override; + layer_tree::Domain* GetLayerTree() override; + memory::Domain* GetMemory() override; + network::Domain* GetNetwork() override; + page::Domain* GetPage() override; + profiler::Domain* GetProfiler() override; + rendering::Domain* GetRendering() override; + runtime::Domain* GetRuntime() override; + security::Domain* GetSecurity() override; + service_worker::Domain* GetServiceWorker() override; + tracing::Domain* GetTracing() override; + worker::Domain* GetWorker() override; + + // content::DevToolstAgentHostClient 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; + + // internal::MessageDispatcher implementation: + void SendMessage(const char* method, + std::unique_ptr<base::Value> params, + base::Callback<void(const base::Value&)> callback) override; + void SendMessage(const char* method, + std::unique_ptr<base::Value> params, + base::Callback<void()> callback) override; + void SendMessage(const char* method, + base::Callback<void(const base::Value&)> callback) override; + void SendMessage(const char* method, + base::Callback<void()> callback) override; + void RegisterEventHandler( + const char* method, + base::Callback<void(const base::Value&)> callback) override; + + void AttachToHost(content::DevToolsAgentHost* agent_host); + void DetachFromHost(content::DevToolsAgentHost* agent_host); + + private: + // Contains a callback with or without a result parameter depending on the + // message type. + struct Callback { + Callback(); + Callback(Callback&& other); + explicit Callback(base::Callback<void()> callback); + explicit Callback(base::Callback<void(const base::Value&)> callback); + ~Callback(); + + Callback& operator=(Callback&& other); + + base::Callback<void()> callback; + base::Callback<void(const base::Value&)> callback_with_result; + }; + + template <typename CallbackType> + void FinalizeAndSendMessage(base::DictionaryValue* message, + CallbackType callback); + + template <typename CallbackType> + void SendMessageWithParams(const char* method, + std::unique_ptr<base::Value> params, + CallbackType callback); + + template <typename CallbackType> + void SendMessageWithoutParams(const char* method, CallbackType callback); + + bool DispatchMessageReply(const base::DictionaryValue& message_dict); + bool DispatchEvent(const base::DictionaryValue& message_dict); + + content::DevToolsAgentHost* agent_host_; // Not owned. + int next_message_id_; + std::unordered_map<int, Callback> pending_messages_; + std::unordered_map<std::string, base::Callback<void(const base::Value&)>> + event_handlers_; + + accessibility::ExperimentalDomain accessibility_domain_; + animation::ExperimentalDomain animation_domain_; + application_cache::ExperimentalDomain application_cache_domain_; + cache_storage::ExperimentalDomain cache_storage_domain_; + console::ExperimentalDomain console_domain_; + css::ExperimentalDomain css_domain_; + database::ExperimentalDomain database_domain_; + debugger::ExperimentalDomain debugger_domain_; + device_orientation::ExperimentalDomain device_orientation_domain_; + dom_debugger::ExperimentalDomain dom_debugger_domain_; + dom::ExperimentalDomain dom_domain_; + dom_storage::ExperimentalDomain dom_storage_domain_; + emulation::ExperimentalDomain emulation_domain_; + heap_profiler::ExperimentalDomain heap_profiler_domain_; + indexeddb::ExperimentalDomain indexeddb_domain_; + input::ExperimentalDomain input_domain_; + inspector::ExperimentalDomain inspector_domain_; + io::ExperimentalDomain io_domain_; + layer_tree::ExperimentalDomain layer_tree_domain_; + memory::ExperimentalDomain memory_domain_; + network::ExperimentalDomain network_domain_; + page::ExperimentalDomain page_domain_; + profiler::ExperimentalDomain profiler_domain_; + rendering::ExperimentalDomain rendering_domain_; + runtime::ExperimentalDomain runtime_domain_; + security::ExperimentalDomain security_domain_; + service_worker::ExperimentalDomain service_worker_domain_; + tracing::ExperimentalDomain tracing_domain_; + worker::ExperimentalDomain worker_domain_; + + DISALLOW_COPY_AND_ASSIGN(HeadlessDevToolsClientImpl); +}; + +} // namespace headless + +#endif // HEADLESS_LIB_BROWSER_HEADLESS_DEVTOOLS_CLIENT_IMPL_H_ diff --git a/chromium/headless/lib/browser/headless_screen.cc b/chromium/headless/lib/browser/headless_screen.cc index 6d6f6074c73..e7be71a515b 100644 --- a/chromium/headless/lib/browser/headless_screen.cc +++ b/chromium/headless/lib/browser/headless_screen.cc @@ -12,18 +12,18 @@ #include "ui/aura/window_event_dispatcher.h" #include "ui/aura/window_tree_host.h" #include "ui/base/ime/input_method.h" +#include "ui/display/screen.h" #include "ui/gfx/geometry/rect_conversions.h" #include "ui/gfx/geometry/size_conversions.h" #include "ui/gfx/native_widget_types.h" -#include "ui/gfx/screen.h" namespace headless { namespace { -bool IsRotationPortrait(gfx::Display::Rotation rotation) { - return rotation == gfx::Display::ROTATE_90 || - rotation == gfx::Display::ROTATE_270; +bool IsRotationPortrait(display::Display::Rotation rotation) { + return rotation == display::Display::ROTATE_90 || + rotation == display::Display::ROTATE_270; } } // namespace @@ -52,7 +52,7 @@ void HeadlessScreen::SetDeviceScaleFactor(float device_scale_factor) { display_.SetScaleAndBounds(device_scale_factor, bounds_in_pixel); } -void HeadlessScreen::SetDisplayRotation(gfx::Display::Rotation rotation) { +void HeadlessScreen::SetDisplayRotation(display::Display::Rotation rotation) { gfx::Rect bounds_in_pixel(display_.GetSizeInPixel()); gfx::Rect new_bounds(bounds_in_pixel); if (IsRotationPortrait(rotation) != IsRotationPortrait(display_.rotation())) { @@ -80,17 +80,17 @@ void HeadlessScreen::SetWorkAreaInsets(const gfx::Insets& insets) { gfx::Transform HeadlessScreen::GetRotationTransform() const { gfx::Transform rotate; switch (display_.rotation()) { - case gfx::Display::ROTATE_0: + case display::Display::ROTATE_0: break; - case gfx::Display::ROTATE_90: + case display::Display::ROTATE_90: rotate.Translate(display_.bounds().height(), 0); rotate.Rotate(90); break; - case gfx::Display::ROTATE_270: + case display::Display::ROTATE_270: rotate.Translate(0, display_.bounds().width()); rotate.Rotate(270); break; - case gfx::Display::ROTATE_180: + case display::Display::ROTATE_180: rotate.Translate(display_.bounds().width(), display_.bounds().height()); rotate.Rotate(180); break; @@ -122,8 +122,8 @@ gfx::Point HeadlessScreen::GetCursorScreenPoint() { return aura::Env::GetInstance()->last_mouse_location(); } -gfx::NativeWindow HeadlessScreen::GetWindowUnderCursor() { - return GetWindowAtScreenPoint(GetCursorScreenPoint()); +bool HeadlessScreen::IsWindowUnderCursor(gfx::NativeWindow window) { + return GetWindowAtScreenPoint(GetCursorScreenPoint()) == window; } gfx::NativeWindow HeadlessScreen::GetWindowAtScreenPoint( @@ -137,32 +137,32 @@ int HeadlessScreen::GetNumDisplays() const { return 1; } -std::vector<gfx::Display> HeadlessScreen::GetAllDisplays() const { - return std::vector<gfx::Display>(1, display_); +std::vector<display::Display> HeadlessScreen::GetAllDisplays() const { + return std::vector<display::Display>(1, display_); } -gfx::Display HeadlessScreen::GetDisplayNearestWindow( +display::Display HeadlessScreen::GetDisplayNearestWindow( gfx::NativeWindow window) const { return display_; } -gfx::Display HeadlessScreen::GetDisplayNearestPoint( +display::Display HeadlessScreen::GetDisplayNearestPoint( const gfx::Point& point) const { return display_; } -gfx::Display HeadlessScreen::GetDisplayMatching( +display::Display HeadlessScreen::GetDisplayMatching( const gfx::Rect& match_rect) const { return display_; } -gfx::Display HeadlessScreen::GetPrimaryDisplay() const { +display::Display HeadlessScreen::GetPrimaryDisplay() const { return display_; } -void HeadlessScreen::AddObserver(gfx::DisplayObserver* observer) {} +void HeadlessScreen::AddObserver(display::DisplayObserver* observer) {} -void HeadlessScreen::RemoveObserver(gfx::DisplayObserver* observer) {} +void HeadlessScreen::RemoveObserver(display::DisplayObserver* observer) {} HeadlessScreen::HeadlessScreen(const gfx::Rect& screen_bounds) : host_(NULL), ui_scale_(1.0f) { diff --git a/chromium/headless/lib/browser/headless_screen.h b/chromium/headless/lib/browser/headless_screen.h index c6b6b3be01b..f0c363c54f6 100644 --- a/chromium/headless/lib/browser/headless_screen.h +++ b/chromium/headless/lib/browser/headless_screen.h @@ -8,8 +8,8 @@ #include "base/compiler_specific.h" #include "base/macros.h" #include "ui/aura/window_observer.h" -#include "ui/gfx/display.h" -#include "ui/gfx/screen.h" +#include "ui/display/display.h" +#include "ui/display/screen.h" namespace gfx { class Insets; @@ -19,22 +19,23 @@ class Transform; namespace aura { class Window; +class WindowTreeClient; class WindowTreeHost; } namespace headless { -class HeadlessScreen : public gfx::Screen, public aura::WindowObserver { +class HeadlessScreen : public display::Screen, public aura::WindowObserver { public: - // Creates a gfx::Screen of the specified size. If no size is specified, then - // creates a 800x600 screen. |size| is in physical pixels. + // Creates a display::Screen of the specified size. If no size is specified, + // then creates a 800x600 screen. |size| is in physical pixels. static HeadlessScreen* Create(const gfx::Size& size); ~HeadlessScreen() override; aura::WindowTreeHost* CreateHostForPrimaryDisplay(); void SetDeviceScaleFactor(float device_scale_fator); - void SetDisplayRotation(gfx::Display::Rotation rotation); + void SetDisplayRotation(display::Display::Rotation rotation); void SetUIScale(float ui_scale); void SetWorkAreaInsets(const gfx::Insets& insets); @@ -48,24 +49,26 @@ class HeadlessScreen : public gfx::Screen, public aura::WindowObserver { const gfx::Rect& new_bounds) override; void OnWindowDestroying(aura::Window* window) override; - // gfx::Screen overrides: + // display::Screen overrides: gfx::Point GetCursorScreenPoint() override; - gfx::NativeWindow GetWindowUnderCursor() override; + bool IsWindowUnderCursor(gfx::NativeWindow window) override; gfx::NativeWindow GetWindowAtScreenPoint(const gfx::Point& point) override; int GetNumDisplays() const override; - std::vector<gfx::Display> GetAllDisplays() const override; - gfx::Display GetDisplayNearestWindow(gfx::NativeView view) const override; - gfx::Display GetDisplayNearestPoint(const gfx::Point& point) const override; - gfx::Display GetDisplayMatching(const gfx::Rect& match_rect) const override; - gfx::Display GetPrimaryDisplay() const override; - void AddObserver(gfx::DisplayObserver* observer) override; - void RemoveObserver(gfx::DisplayObserver* observer) override; + std::vector<display::Display> GetAllDisplays() const override; + display::Display GetDisplayNearestWindow(gfx::NativeView view) const override; + display::Display GetDisplayNearestPoint( + const gfx::Point& point) const override; + display::Display GetDisplayMatching( + const gfx::Rect& match_rect) const override; + display::Display GetPrimaryDisplay() const override; + void AddObserver(display::DisplayObserver* observer) override; + void RemoveObserver(display::DisplayObserver* observer) override; private: explicit HeadlessScreen(const gfx::Rect& screen_bounds); aura::WindowTreeHost* host_; - gfx::Display display_; + display::Display display_; float ui_scale_; DISALLOW_COPY_AND_ASSIGN(HeadlessScreen); 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 1a46e1064a1..418b63f3416 100644 --- a/chromium/headless/lib/browser/headless_url_request_context_getter.cc +++ b/chromium/headless/lib/browser/headless_url_request_context_getter.cc @@ -6,7 +6,6 @@ #include <memory> -#include "base/command_line.h" #include "base/memory/ptr_util.h" #include "base/single_thread_task_runner.h" #include "base/threading/worker_pool.h" @@ -105,9 +104,6 @@ HeadlessURLRequestContextGetter::GetURLRequestContext() { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); if (!url_request_context_) { - const base::CommandLine& command_line = - *base::CommandLine::ForCurrentProcess(); - url_request_context_.reset(new net::URLRequestContext()); url_request_context_->set_net_log(net_log_); network_delegate_ = CreateNetworkDelegate(); @@ -162,11 +158,10 @@ HeadlessURLRequestContextGetter::GetURLRequestContext() { network_session_params.net_log = url_request_context_->net_log(); network_session_params.ignore_certificate_errors = ignore_certificate_errors_; - if (command_line.HasSwitch(switches::kHostResolverRules)) { + if (!options_.host_resolver_rules.empty()) { std::unique_ptr<net::MappedHostResolver> mapped_host_resolver( new net::MappedHostResolver(std::move(host_resolver))); - mapped_host_resolver->SetRulesFromString( - command_line.GetSwitchValueASCII(switches::kHostResolverRules)); + mapped_host_resolver->SetRulesFromString(options_.host_resolver_rules); host_resolver = std::move(mapped_host_resolver); } diff --git a/chromium/headless/lib/browser/headless_web_contents_impl.cc b/chromium/headless/lib/browser/headless_web_contents_impl.cc index f58dfe0671c..13ed5e1076c 100644 --- a/chromium/headless/lib/browser/headless_web_contents_impl.cc +++ b/chromium/headless/lib/browser/headless_web_contents_impl.cc @@ -8,6 +8,7 @@ #include "base/memory/ptr_util.h" #include "base/memory/weak_ptr.h" #include "base/trace_event/trace_event.h" +#include "content/public/browser/devtools_agent_host.h" #include "content/public/browser/navigation_handle.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" @@ -19,6 +20,8 @@ #include "content/public/common/bindings_policy.h" #include "content/public/common/service_registry.h" #include "content/public/renderer/render_frame.h" +#include "headless/lib/browser/headless_browser_impl.h" +#include "headless/lib/browser/headless_devtools_client_impl.h" #include "ui/aura/window.h" namespace headless { @@ -31,16 +34,9 @@ class WebContentsObserverAdapter : public content::WebContentsObserver { ~WebContentsObserverAdapter() override {} - void RenderViewReady() override { observer_->WebContentsReady(); } - - void DocumentOnLoadCompletedInMainFrame() override { - observer_->DocumentOnLoadCompletedInMainFrame(); - } - - void DidFinishNavigation( - content::NavigationHandle* navigation_handle) override { - observer_->DidFinishNavigation(navigation_handle->HasCommitted() && - !navigation_handle->IsErrorPage()); + void RenderViewReady() override { + DCHECK(web_contents()->GetMainFrame()->IsRenderFrameLive()); + observer_->DevToolsTargetReady(); } private: @@ -51,23 +47,53 @@ class WebContentsObserverAdapter : public content::WebContentsObserver { class HeadlessWebContentsImpl::Delegate : public content::WebContentsDelegate { public: - Delegate() {} + explicit Delegate(HeadlessBrowserImpl* browser) : browser_(browser) {} + + void WebContentsCreated(content::WebContents* source_contents, + int opener_render_frame_id, + const std::string& frame_name, + const GURL& target_url, + content::WebContents* new_contents) override { + browser_->RegisterWebContents( + HeadlessWebContentsImpl::CreateFromWebContents(new_contents, browser_)); + } private: + HeadlessBrowserImpl* browser_; // Not owned. DISALLOW_COPY_AND_ASSIGN(Delegate); }; -HeadlessWebContentsImpl::HeadlessWebContentsImpl( - content::BrowserContext* browser_context, +// static +std::unique_ptr<HeadlessWebContentsImpl> HeadlessWebContentsImpl::Create( + content::BrowserContext* context, aura::Window* parent_window, - const gfx::Size& initial_size) - : web_contents_delegate_(new HeadlessWebContentsImpl::Delegate()) { - content::WebContents::CreateParams create_params(browser_context, nullptr); + const gfx::Size& initial_size, + HeadlessBrowserImpl* browser) { + content::WebContents::CreateParams create_params(context, nullptr); create_params.initial_size = initial_size; - web_contents_.reset(content::WebContents::Create(create_params)); - web_contents_->SetDelegate(web_contents_delegate_.get()); + std::unique_ptr<HeadlessWebContentsImpl> headless_web_contents = + base::WrapUnique(new HeadlessWebContentsImpl( + content::WebContents::Create(create_params), browser)); + + headless_web_contents->InitializeScreen(parent_window, initial_size); + + return headless_web_contents; +} + +// static +std::unique_ptr<HeadlessWebContentsImpl> +HeadlessWebContentsImpl::CreateFromWebContents( + content::WebContents* web_contents, + HeadlessBrowserImpl* browser) { + std::unique_ptr<HeadlessWebContentsImpl> headless_web_contents = + base::WrapUnique(new HeadlessWebContentsImpl(web_contents, browser)); + + return headless_web_contents; +} +void HeadlessWebContentsImpl::InitializeScreen(aura::Window* parent_window, + const gfx::Size& initial_size) { aura::Window* contents = web_contents_->GetNativeView(); DCHECK(!parent_window->Contains(contents)); parent_window->AddChild(contents); @@ -80,6 +106,15 @@ HeadlessWebContentsImpl::HeadlessWebContentsImpl( host_view->SetSize(initial_size); } +HeadlessWebContentsImpl::HeadlessWebContentsImpl( + content::WebContents* web_contents, + HeadlessBrowserImpl* browser) + : web_contents_delegate_(new HeadlessWebContentsImpl::Delegate(browser)), + web_contents_(web_contents), + browser_(browser) { + web_contents_->SetDelegate(web_contents_delegate_.get()); +} + HeadlessWebContentsImpl::~HeadlessWebContentsImpl() { web_contents_->Close(); } @@ -95,6 +130,10 @@ bool HeadlessWebContentsImpl::OpenURL(const GURL& url) { return true; } +void HeadlessWebContentsImpl::Close() { + browser_->DestroyWebContents(this); +} + void HeadlessWebContentsImpl::AddObserver(Observer* observer) { DCHECK(observer_map_.find(observer) == observer_map_.end()); observer_map_[observer] = base::WrapUnique( @@ -107,6 +146,21 @@ void HeadlessWebContentsImpl::RemoveObserver(Observer* observer) { observer_map_.erase(it); } +HeadlessDevToolsTarget* HeadlessWebContentsImpl::GetDevToolsTarget() { + return web_contents()->GetMainFrame()->IsRenderFrameLive() ? this : nullptr; +} + +void HeadlessWebContentsImpl::AttachClient(HeadlessDevToolsClient* client) { + if (!agent_host_) + agent_host_ = content::DevToolsAgentHost::GetOrCreateFor(web_contents()); + HeadlessDevToolsClientImpl::From(client)->AttachToHost(agent_host_.get()); +} + +void HeadlessWebContentsImpl::DetachClient(HeadlessDevToolsClient* client) { + DCHECK(agent_host_); + HeadlessDevToolsClientImpl::From(client)->DetachFromHost(agent_host_.get()); +} + content::WebContents* HeadlessWebContentsImpl::web_contents() const { return web_contents_.get(); } diff --git a/chromium/headless/lib/browser/headless_web_contents_impl.h b/chromium/headless/lib/browser/headless_web_contents_impl.h index e1cdc2d1615..3d2b7654361 100644 --- a/chromium/headless/lib/browser/headless_web_contents_impl.h +++ b/chromium/headless/lib/browser/headless_web_contents_impl.h @@ -5,6 +5,7 @@ #ifndef HEADLESS_LIB_BROWSER_HEADLESS_WEB_CONTENTS_IMPL_H_ #define HEADLESS_LIB_BROWSER_HEADLESS_WEB_CONTENTS_IMPL_H_ +#include "headless/public/headless_devtools_target.h" #include "headless/public/headless_web_contents.h" #include <memory> @@ -15,8 +16,9 @@ class Window; } namespace content { -class WebContents; class BrowserContext; +class DevToolsAgentHost; +class WebContents; } namespace gfx { @@ -24,26 +26,54 @@ class Size; } namespace headless { +class HeadlessDevToolsHostImpl; +class HeadlessBrowserImpl; class WebContentsObserverAdapter; -class HeadlessWebContentsImpl : public HeadlessWebContents { +class HeadlessWebContentsImpl : public HeadlessWebContents, + public HeadlessDevToolsTarget { public: - HeadlessWebContentsImpl(content::BrowserContext* context, - aura::Window* parent_window, - const gfx::Size& initial_size); ~HeadlessWebContentsImpl() override; + static std::unique_ptr<HeadlessWebContentsImpl> Create( + content::BrowserContext* context, + aura::Window* parent_window, + const gfx::Size& initial_size, + HeadlessBrowserImpl* browser); + + // Takes ownership of |web_contents|. + static std::unique_ptr<HeadlessWebContentsImpl> CreateFromWebContents( + content::WebContents* web_contents, + HeadlessBrowserImpl* browser); + // HeadlessWebContents implementation: void AddObserver(Observer* observer) override; void RemoveObserver(Observer* observer) override; + HeadlessDevToolsTarget* GetDevToolsTarget() override; + + // HeadlessDevToolsTarget implementation: + void AttachClient(HeadlessDevToolsClient* client) override; + void DetachClient(HeadlessDevToolsClient* client) override; content::WebContents* web_contents() const; bool OpenURL(const GURL& url); + void Close() override; + private: + // Takes ownership of |web_contents|. + HeadlessWebContentsImpl(content::WebContents* web_contents, + HeadlessBrowserImpl* browser); + + void InitializeScreen(aura::Window* parent_window, + const gfx::Size& initial_size); + class Delegate; std::unique_ptr<Delegate> web_contents_delegate_; std::unique_ptr<content::WebContents> web_contents_; + scoped_refptr<content::DevToolsAgentHost> agent_host_; + + HeadlessBrowserImpl* browser_; // Not owned. using ObserverMap = std::unordered_map<HeadlessWebContents::Observer*, diff --git a/chromium/headless/lib/browser/headless_window_tree_client.cc b/chromium/headless/lib/browser/headless_window_tree_client.cc new file mode 100644 index 00000000000..be797ce3032 --- /dev/null +++ b/chromium/headless/lib/browser/headless_window_tree_client.cc @@ -0,0 +1,27 @@ +// Copyright (c) 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "headless/lib/browser/headless_window_tree_client.h" + +#include "ui/aura/window.h" + +namespace headless { + +HeadlessWindowTreeClient::HeadlessWindowTreeClient(aura::Window* root_window) + : root_window_(root_window) { + aura::client::SetWindowTreeClient(root_window_, this); +} + +HeadlessWindowTreeClient::~HeadlessWindowTreeClient() { + aura::client::SetWindowTreeClient(root_window_, nullptr); +} + +aura::Window* HeadlessWindowTreeClient::GetDefaultParent( + aura::Window* context, + aura::Window* window, + const gfx::Rect& bounds) { + return root_window_; +} + +} // namespace headless diff --git a/chromium/headless/lib/browser/headless_window_tree_client.h b/chromium/headless/lib/browser/headless_window_tree_client.h new file mode 100644 index 00000000000..c64e3daf92b --- /dev/null +++ b/chromium/headless/lib/browser/headless_window_tree_client.h @@ -0,0 +1,30 @@ +// Copyright (c) 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef HEADLESS_LIB_BROWSER_HEADLESS_WINDOW_TREE_CLIENT_H_ +#define HEADLESS_LIB_BROWSER_HEADLESS_WINDOW_TREE_CLIENT_H_ + +#include "base/macros.h" +#include "ui/aura/client/window_tree_client.h" + +namespace headless { + +class HeadlessWindowTreeClient : public aura::client::WindowTreeClient { + public: + explicit HeadlessWindowTreeClient(aura::Window* root_window); + ~HeadlessWindowTreeClient() override; + + aura::Window* GetDefaultParent(aura::Window* context, + aura::Window* window, + const gfx::Rect& bounds) override; + + private: + aura::Window* root_window_; // Not owned. + + DISALLOW_COPY_AND_ASSIGN(HeadlessWindowTreeClient); +}; + +} // namespace headless + +#endif // HEADLESS_LIB_BROWSER_HEADLESS_WINDOW_TREE_CLIENT_H_ diff --git a/chromium/headless/lib/browser/type_conversions_h.template b/chromium/headless/lib/browser/type_conversions_h.template new file mode 100644 index 00000000000..286b0e5ea65 --- /dev/null +++ b/chromium/headless/lib/browser/type_conversions_h.template @@ -0,0 +1,77 @@ +// This file is generated + +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef HEADLESS_PUBLIC_DOMAINS_TYPE_CONVERSIONS_H_ +#define HEADLESS_PUBLIC_DOMAINS_TYPE_CONVERSIONS_H_ + +#include "headless/public/domains/types.h" +#include "headless/public/internal/value_conversions.h" + +namespace headless { +namespace internal { + +{% for domain in api.domains %} + {% for type in domain.types %} + {% set namespace = domain.domain | camelcase_to_hacker_style %} + {% if "enum" in type %} +template <> +struct FromValue<{{namespace}}::{{type.id}}> { + static {{namespace}}::{{type.id}} Parse(const base::Value& value, ErrorReporter* errors) { + {% set default = namespace + '::' + type.id + '::' + type.enum[0] | dash_to_camelcase | camelcase_to_hacker_style | upper | mangle_enum %} + std::string string_value; + if (!value.GetAsString(&string_value)) { + 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}}") + return {{namespace}}::{{type.id}}::{{literal | dash_to_camelcase | camelcase_to_hacker_style | upper | mangle_enum}}; + {% endfor %} + errors->AddError("invalid enum value"); + return {{default}}; + } +}; + +template <typename T> +std::unique_ptr<base::Value> ToValueImpl(const {{namespace}}::{{type.id}}& value, T*) { + switch (value) { + {% for literal in type.enum %} + case {{namespace}}::{{type.id}}::{{literal | dash_to_camelcase | camelcase_to_hacker_style | upper | mangle_enum}}: + return base::WrapUnique(new base::StringValue("{{literal}}")); + {% endfor %} + }; + NOTREACHED(); + return nullptr; +} + {% continue %} + {% endif %} + + {% if not (type.type == "object") or not ("properties" in type) %}{% continue %}{% endif %} +template <> +struct FromValue<{{namespace}}::{{type.id}}> { + static std::unique_ptr<{{namespace}}::{{type.id}}> Parse(const base::Value& value, ErrorReporter* errors) { + return {{namespace}}::{{type.id}}::Parse(value, errors); + } +}; + +template <typename T> +std::unique_ptr<base::Value> ToValueImpl(const {{namespace}}::{{type.id}}& value, T*) { + return value.Serialize(); +} + + {% endfor %} +{% endfor %} + +template <typename T> +std::unique_ptr<base::Value> ToValue(const T& value) { + return ToValueImpl(value, static_cast<T*>(nullptr)); +} + +} // namespace internal +} // namespace headless + +#endif // HEADLESS_PUBLIC_DOMAINS_TYPE_CONVERSIONS_H_ diff --git a/chromium/headless/lib/browser/types_cc.template b/chromium/headless/lib/browser/types_cc.template new file mode 100644 index 00000000000..5be72cf7924 --- /dev/null +++ b/chromium/headless/lib/browser/types_cc.template @@ -0,0 +1,130 @@ +// This file is generated + +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "headless/public/domains/types.h" + +#include "base/memory/ptr_util.h" +#include "headless/public/domains/type_conversions.h" + +namespace headless { + +// ------------- Enum values from types. +{% for domain in api.domains %} + {% continue %} + +namespace internal { + + {% for type in domain.types %} +// {{type}} + {% if type.type == "array" %} +template <> +struct FromValue<{{resolve_type(type).raw_type}}> { + static {{resolve_type(type).raw_type}} Parse(const base::Value& value, ErrorReporter* errors) { + {{resolve_type(type).raw_type}} result; + const base::ListValue* list; + if (!value.GetAsList(&list)) { + errors->AddError("list value expected"); + return result; + } + errors->Push(); + for (const auto& item : *list) + result.push_back(FromValue<{{resolve_type(type).raw_type}}::value_type>::Parse(*item, errors)); + errors->Pop(); + return result; + } +}; + + {% continue %} + {% endif %} +#} + {% if not (type.type == "object") or not ("properties" in type) %}{% continue %}{% endif %} + {% set namespace = domain.domain | camelcase_to_hacker_style %} +template <> +struct FromValue<{{namespace}}::{{type.id}}> { + static std::unique_ptr<{{namespace}}::{{type.id}}> Parse(const base::Value& value, ErrorReporter* errors) { + return {{namespace}}::{{type.id}}::Parse(value, errors); + } +}; + +template <typename T> +std::unique_ptr<base::Value> ToValueImpl(const {{namespace}}::{{type.id}}& value, T*) { + return value.Serialize(); +} + + {% endfor %} +} // namespace internal +{% endfor %} + +{% for domain in api.domains %} + +namespace {{domain.domain | camelcase_to_hacker_style}} { + {% for type in domain.types %} + {% if not (type.type == "object") or not ("properties" in type) %}{% continue %}{% endif %} + +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)) { + errors->AddError("object expected"); + errors->Pop(); + return nullptr; + } + + std::unique_ptr<{{type.id}}> result(new {{type.id}}()); + errors->Push(); + 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}})) { + 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); + {% else %} + result->{{property.name | camelcase_to_hacker_style}}_ = internal::FromValue<{{resolve_type(property).raw_type}}>::Parse(*{{value_name}}, errors); + {% endif %} + {% if property.optional %} + } + {% else %} + } else { + errors->AddError("required property missing: {{property.name}}"); + } + {% endif %} + {% endfor %} + errors->Pop(); + errors->Pop(); + if (errors->HasErrors()) + return nullptr; + return result; +} + +std::unique_ptr<base::Value> {{type.id}}::Serialize() const { + std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue()); + {% for property in type.properties %} + {% set type = resolve_type(property) %} + {% if property.optional %} + if ({{property.name | camelcase_to_hacker_style}}_) + result->Set("{{property.name}}", internal::ToValue({{type.to_raw_type % ("%s_.value()" % property.name | camelcase_to_hacker_style)}})); + {% else %} + result->Set("{{property.name}}", internal::ToValue({{type.to_raw_type % ("%s_" % property.name | camelcase_to_hacker_style)}})); + {% endif %} + {% endfor %} + return std::move(result); +} + +std::unique_ptr<{{type.id}}> {{type.id}}::Clone() const { + ErrorReporter errors; + std::unique_ptr<{{type.id}}> result = Parse(*Serialize(), &errors); + DCHECK(!errors.HasErrors()); + return result; +} + + {% endfor %} +} // namespace {{domain.domain | camelcase_to_hacker_style}} +{% endfor %} + +} // namespace headless diff --git a/chromium/headless/lib/browser/types_h.template b/chromium/headless/lib/browser/types_h.template new file mode 100644 index 00000000000..25b341d89dd --- /dev/null +++ b/chromium/headless/lib/browser/types_h.template @@ -0,0 +1,158 @@ +// This file is generated + +// Copyright (c) 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef HEADLESS_PUBLIC_DOMAINS_TYPES_H_ +#define HEADLESS_PUBLIC_DOMAINS_TYPES_H_ + +#include "base/optional.h" +#include "base/values.h" +#include "headless/public/headless_export.h" +#include "headless/public/util/error_reporter.h" + +#include "base/memory/ptr_util.h" + +namespace headless { + +// ------------- Forward declarations and typedefs. + +{% for domain in api.domains %} + +namespace {{domain.domain | camelcase_to_hacker_style}} { + {% for type in domain.types %} + {% if type.type == "object" %} + {% if "properties" in type %} +class {{type.id}}; + {% else %} +using {{type.id}} = base::Value; + {% endif %} + {% endif %} + {% endfor %} +} // namespace {{domain.domain}} +{% endfor %} + +{% for domain in api.domains %} +namespace {{domain.domain | camelcase_to_hacker_style}} { + {% for type in domain.types %} + {% if "enum" in type %} +enum class {{type.id}} { + {% for literal in type.enum %} + {{ literal | dash_to_camelcase | camelcase_to_hacker_style | upper | mangle_enum}}{{',' if not loop.last}} + {% endfor %} +}; + + {% endif %} + {% endfor %} +} // namespace {{domain.domain | camelcase_to_hacker_style}} + +{% endfor %} + +// ------------- Type and builder declarations. +{% for domain in api.domains %} + +namespace {{domain.domain | camelcase_to_hacker_style}} { + {% for type in domain.types %} + {% if not (type.type == "object") or not ("properties" in type) %}{% continue %}{% endif %} + + {% if type.description %} +// {{type.description}} + {% endif %} +class HEADLESS_EXPORT {{type.id}} { + public: + static {{resolve_type(type).pass_type}} Parse(const base::Value& value, ErrorReporter* errors); + ~{{type.id}}() { } + {% for property in type.properties %} + + {% if property.description %} + // {{property.description}} + {% endif %} + {% if property.optional %} + bool Has{{property.name | to_title_case}}() const { return !!{{property.name | camelcase_to_hacker_style}}_; } + {{resolve_type(property).raw_return_type}} Get{{property.name | to_title_case}}() const { return {{resolve_type(property).to_raw_return_type % ("%s_.value()" % property.name | camelcase_to_hacker_style)}}; } + void Set{{property.name | to_title_case}}({{resolve_type(property).pass_type}} value) { {{property.name | camelcase_to_hacker_style}}_ = {{resolve_type(property).to_pass_type % 'value'}}; } + {% else %} + {{resolve_type(property).raw_return_type}} Get{{property.name | to_title_case}}() const { return {{resolve_type(property).to_raw_return_type % ("%s_" % property.name | camelcase_to_hacker_style)}}; } + void Set{{property.name | to_title_case}}({{resolve_type(property).pass_type}} value) { {{property.name | camelcase_to_hacker_style}}_ = {{resolve_type(property).to_pass_type % 'value'}}; } + {% endif %} + {% endfor %} + + std::unique_ptr<base::Value> Serialize() const; + {{resolve_type(type).pass_type}} Clone() const; + + template<int STATE> + class {{type.id}}Builder { + public: + enum { + kNoFieldsSet = 0, + {% set count = 0 %} + {% for property in type.properties %} + {% if not(property.optional) %} + {% set count = count + 1 %} + k{{property.name | to_title_case}}Set = 1 << {{count}}, + {% endif %} + {% endfor %} + kAllRequiredFieldsSet = ( + {%- for property in type.properties %} + {% if not(property.optional) %}k{{property.name | to_title_case}}Set | {%endif %} + {% endfor %}0) + }; + + {% for property in type.properties %} + {% if property.optional %} + {{type.id}}Builder<STATE>& Set{{property.name | to_title_case}}({{resolve_type(property).pass_type}} value) { + result_->Set{{property.name | to_title_case}}({{resolve_type(property).to_pass_type % 'value'}}); + return *this; + } + {% else %} + {{type.id}}Builder<STATE | k{{property.name | to_title_case}}Set>& Set{{property.name | to_title_case}}({{resolve_type(property).pass_type}} value) { + static_assert(!(STATE & k{{property.name | to_title_case}}Set), "property {{property.name}} should not have already been set"); + result_->Set{{property.name | to_title_case}}({{resolve_type(property).to_pass_type % 'value'}}); + return CastState<k{{property.name | to_title_case}}Set>(); + } + {% endif %} + + {% endfor %} + {{resolve_type(type).pass_type}} Build() { + static_assert(STATE == kAllRequiredFieldsSet, "all required fields should have been set"); + return std::move(result_); + } + + private: + friend class {{type.id}}; + {{type.id}}Builder() : result_(new {{type.id}}()) { } + + template<int STEP> {{type.id}}Builder<STATE | STEP>& CastState() { + return *reinterpret_cast<{{type.id}}Builder<STATE | STEP>*>(this); + } + + {{resolve_type(type).type}} result_; + }; + + static {{type.id}}Builder<0> Builder() { + return {{type.id}}Builder<0>(); + } + + private: + {{type.id}}() { } + + {% for property in type.properties %} + {% if property.optional %} + base::Optional<{{resolve_type(property).type}}> {{property.name | camelcase_to_hacker_style}}_; + {% else %} + {{resolve_type(property).type}} {{property.name | camelcase_to_hacker_style}}_; + {% endif %} + {% endfor %} + + DISALLOW_COPY_AND_ASSIGN({{type.id}}); +}; + + {% endfor %} + +} // namespace {{domain.domain | camelcase_to_hacker_style}} +{% endfor %} + +} // namespace headless + +#endif // HEADLESS_PUBLIC_DOMAINS_TYPES_H_ diff --git a/chromium/headless/lib/headless_browser_browsertest.cc b/chromium/headless/lib/headless_browser_browsertest.cc index 9cc594073ce..d6eb82b21e9 100644 --- a/chromium/headless/lib/headless_browser_browsertest.cc +++ b/chromium/headless/lib/headless_browser_browsertest.cc @@ -4,6 +4,7 @@ #include <memory> +#include "base/strings/stringprintf.h" #include "content/public/test/browser_test.h" #include "headless/public/headless_browser.h" #include "headless/public/headless_web_contents.h" @@ -15,18 +16,24 @@ namespace headless { IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, CreateAndDestroyWebContents) { - std::unique_ptr<HeadlessWebContents> web_contents = + HeadlessWebContents* web_contents = browser()->CreateWebContents(GURL("about:blank"), gfx::Size(800, 600)); EXPECT_TRUE(web_contents); + + EXPECT_EQ(static_cast<size_t>(1), browser()->GetAllWebContents().size()); + EXPECT_EQ(web_contents, browser()->GetAllWebContents()[0]); // TODO(skyostil): Verify viewport dimensions once we can. - web_contents.reset(); + web_contents->Close(); + + EXPECT_TRUE(browser()->GetAllWebContents().empty()); } IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, CreateWithBadURL) { GURL bad_url("not_valid"); - std::unique_ptr<HeadlessWebContents> web_contents = + HeadlessWebContents* web_contents = browser()->CreateWebContents(bad_url, gfx::Size(800, 600)); EXPECT_FALSE(web_contents); + EXPECT_TRUE(browser()->GetAllWebContents().empty()); } class HeadlessBrowserTestWithProxy : public HeadlessBrowserTest { @@ -60,11 +67,31 @@ IN_PROC_BROWSER_TEST_F(HeadlessBrowserTestWithProxy, SetProxyServer) { // Load a page which doesn't actually exist, but for which the our proxy // returns valid content anyway. - std::unique_ptr<HeadlessWebContents> web_contents = - browser()->CreateWebContents( - GURL("http://not-an-actual-domain.tld/hello.html"), - gfx::Size(800, 600)); - EXPECT_TRUE(WaitForLoad(web_contents.get())); + // + // TODO(altimin): Currently this construction does not serve hello.html + // from headless/test/data as expected. We should fix this. + HeadlessWebContents* web_contents = browser()->CreateWebContents( + GURL("http://not-an-actual-domain.tld/hello.html"), gfx::Size(800, 600)); + EXPECT_TRUE(WaitForLoad(web_contents)); + EXPECT_EQ(static_cast<size_t>(1), browser()->GetAllWebContents().size()); + EXPECT_EQ(web_contents, browser()->GetAllWebContents()[0]); + web_contents->Close(); + EXPECT_TRUE(browser()->GetAllWebContents().empty()); +} + +IN_PROC_BROWSER_TEST_F(HeadlessBrowserTest, SetHostResolverRules) { + EXPECT_TRUE(embedded_test_server()->Start()); + HeadlessBrowser::Options::Builder builder; + builder.SetHostResolverRules( + base::StringPrintf("MAP not-an-actual-domain.tld 127.0.0.1:%d", + embedded_test_server()->host_port_pair().port())); + SetBrowserOptions(builder.Build()); + + // Load a page which doesn't actually exist, but which is turned into a valid + // address by our host resolver rules. + HeadlessWebContents* web_contents = browser()->CreateWebContents( + GURL("http://not-an-actual-domain.tld/hello.html"), gfx::Size(800, 600)); + EXPECT_TRUE(WaitForLoad(web_contents)); } } // namespace headless diff --git a/chromium/headless/lib/headless_content_main_delegate.cc b/chromium/headless/lib/headless_content_main_delegate.cc index 245341fcb43..e7ea8d7690d 100644 --- a/chromium/headless/lib/headless_content_main_delegate.cc +++ b/chromium/headless/lib/headless_content_main_delegate.cc @@ -41,8 +41,8 @@ HeadlessContentMainDelegate::~HeadlessContentMainDelegate() { bool HeadlessContentMainDelegate::BasicStartupComplete(int* exit_code) { base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - command_line->AppendSwitch(switches::kNoSandbox); - command_line->AppendSwitch(switches::kSingleProcess); + if (browser_->options().single_process_mode) + command_line->AppendSwitch(switches::kSingleProcess); // The headless backend is automatically chosen for a headless build, but also // adding it here allows us to run in a non-headless build too. diff --git a/chromium/headless/lib/headless_devtools_client_browsertest.cc b/chromium/headless/lib/headless_devtools_client_browsertest.cc new file mode 100644 index 00000000000..5e073a8f31e --- /dev/null +++ b/chromium/headless/lib/headless_devtools_client_browsertest.cc @@ -0,0 +1,213 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <memory> + +#include "content/public/test/browser_test.h" +#include "headless/public/domains/network.h" +#include "headless/public/domains/page.h" +#include "headless/public/domains/runtime.h" +#include "headless/public/headless_browser.h" +#include "headless/public/headless_devtools_client.h" +#include "headless/public/headless_devtools_target.h" +#include "headless/public/headless_web_contents.h" +#include "headless/test/headless_browser_test.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/size.h" +#include "url/gurl.h" + +namespace headless { + +#define DEVTOOLS_CLIENT_TEST_F(TEST_FIXTURE_NAME) \ + IN_PROC_BROWSER_TEST_F(TEST_FIXTURE_NAME, RunAsyncTest) { RunTest(); } \ + class AsyncHeadlessBrowserTestNeedsSemicolon##TEST_FIXTURE_NAME {} + +// A test fixture which attaches a devtools client before starting the test. +class HeadlessDevToolsClientTest : public HeadlessBrowserTest, + public HeadlessWebContents::Observer { + public: + HeadlessDevToolsClientTest() + : devtools_client_(HeadlessDevToolsClient::Create()) {} + ~HeadlessDevToolsClientTest() override {} + + // HeadlessWebContentsObserver implementation: + void DevToolsTargetReady() override { + EXPECT_TRUE(web_contents_->GetDevToolsTarget()); + web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get()); + RunDevToolsClientTest(); + } + + virtual void RunDevToolsClientTest() = 0; + + protected: + void RunTest() { + web_contents_ = + browser()->CreateWebContents(GURL("about:blank"), gfx::Size(800, 600)); + web_contents_->AddObserver(this); + + RunAsynchronousTest(); + + web_contents_->GetDevToolsTarget()->DetachClient(devtools_client_.get()); + web_contents_->RemoveObserver(this); + web_contents_->Close(); + web_contents_ = nullptr; + } + + HeadlessWebContents* web_contents_; + std::unique_ptr<HeadlessDevToolsClient> devtools_client_; +}; + +class HeadlessDevToolsClientNavigationTest : public HeadlessDevToolsClientTest, + page::ExperimentalObserver { + public: + void RunDevToolsClientTest() override { + EXPECT_TRUE(embedded_test_server()->Start()); + std::unique_ptr<page::NavigateParams> params = + page::NavigateParams::Builder() + .SetUrl(embedded_test_server()->GetURL("/hello.html").spec()) + .Build(); + devtools_client_->GetPage()->GetExperimental()->AddObserver(this); + devtools_client_->GetPage()->Enable(); + devtools_client_->GetPage()->Navigate(std::move(params)); + } + + void OnLoadEventFired(const page::LoadEventFiredParams& params) override { + devtools_client_->GetPage()->GetExperimental()->RemoveObserver(this); + FinishAsynchronousTest(); + } + + // Check that events with no parameters still get a parameters object. + void OnFrameResized(const page::FrameResizedParams& params) override {} +}; + +DEVTOOLS_CLIENT_TEST_F(HeadlessDevToolsClientNavigationTest); + +class HeadlessDevToolsClientEvalTest : public HeadlessDevToolsClientTest { + public: + void RunDevToolsClientTest() override { + std::unique_ptr<runtime::EvaluateParams> params = + runtime::EvaluateParams::Builder().SetExpression("1 + 2").Build(); + devtools_client_->GetRuntime()->Evaluate( + std::move(params), + base::Bind(&HeadlessDevToolsClientEvalTest::OnFirstResult, + base::Unretained(this))); + // Test the convenience overload which only takes the required command + // parameters. + devtools_client_->GetRuntime()->Evaluate( + "24 * 7", base::Bind(&HeadlessDevToolsClientEvalTest::OnSecondResult, + base::Unretained(this))); + } + + 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); + } + + 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); + FinishAsynchronousTest(); + } +}; + +DEVTOOLS_CLIENT_TEST_F(HeadlessDevToolsClientEvalTest); + +class HeadlessDevToolsClientCallbackTest : public HeadlessDevToolsClientTest { + public: + HeadlessDevToolsClientCallbackTest() : first_result_received_(false) {} + + void RunDevToolsClientTest() override { + // Null callback without parameters. + devtools_client_->GetPage()->Enable(); + // Null callback with parameters. + devtools_client_->GetRuntime()->Evaluate("true"); + // Non-null callback without parameters. + devtools_client_->GetPage()->Disable( + base::Bind(&HeadlessDevToolsClientCallbackTest::OnFirstResult, + base::Unretained(this))); + // Non-null callback with parameters. + devtools_client_->GetRuntime()->Evaluate( + "true", base::Bind(&HeadlessDevToolsClientCallbackTest::OnSecondResult, + base::Unretained(this))); + } + + void OnFirstResult() { + EXPECT_FALSE(first_result_received_); + first_result_received_ = true; + } + + void OnSecondResult(std::unique_ptr<runtime::EvaluateResult> result) { + EXPECT_TRUE(first_result_received_); + FinishAsynchronousTest(); + } + + private: + bool first_result_received_; +}; + +DEVTOOLS_CLIENT_TEST_F(HeadlessDevToolsClientCallbackTest); + +class HeadlessDevToolsClientObserverTest : public HeadlessDevToolsClientTest, + network::Observer { + public: + void RunDevToolsClientTest() override { + EXPECT_TRUE(embedded_test_server()->Start()); + devtools_client_->GetNetwork()->AddObserver(this); + devtools_client_->GetNetwork()->Enable(); + devtools_client_->GetPage()->Navigate( + embedded_test_server()->GetURL("/hello.html").spec()); + } + + void OnRequestWillBeSent( + const network::RequestWillBeSentParams& params) override { + EXPECT_EQ("GET", params.GetRequest()->GetMethod()); + EXPECT_EQ(embedded_test_server()->GetURL("/hello.html").spec(), + params.GetRequest()->GetUrl()); + } + + void OnResponseReceived( + 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); + + devtools_client_->GetNetwork()->RemoveObserver(this); + FinishAsynchronousTest(); + } +}; + +DEVTOOLS_CLIENT_TEST_F(HeadlessDevToolsClientObserverTest); + +class HeadlessDevToolsClientExperimentalTest + : public HeadlessDevToolsClientTest, + page::ExperimentalObserver { + public: + void RunDevToolsClientTest() override { + EXPECT_TRUE(embedded_test_server()->Start()); + // Check that experimental commands require parameter objects. + devtools_client_->GetRuntime()->GetExperimental()->Run( + runtime::RunParams::Builder().Build()); + + devtools_client_->GetPage()->GetExperimental()->AddObserver(this); + devtools_client_->GetPage()->Enable(); + devtools_client_->GetPage()->Navigate( + embedded_test_server()->GetURL("/hello.html").spec()); + } + + void OnFrameStoppedLoading( + const page::FrameStoppedLoadingParams& params) override { + FinishAsynchronousTest(); + } +}; + +DEVTOOLS_CLIENT_TEST_F(HeadlessDevToolsClientExperimentalTest); + +} // namespace headless diff --git a/chromium/headless/lib/headless_web_contents_browsertest.cc b/chromium/headless/lib/headless_web_contents_browsertest.cc index aaa5491882b..30faee5e65e 100644 --- a/chromium/headless/lib/headless_web_contents_browsertest.cc +++ b/chromium/headless/lib/headless_web_contents_browsertest.cc @@ -16,39 +16,30 @@ namespace headless { class HeadlessWebContentsTest : public HeadlessBrowserTest {}; -class NavigationObserver : public HeadlessWebContents::Observer { - public: - NavigationObserver(HeadlessWebContentsTest* browser_test) - : browser_test_(browser_test), navigation_succeeded_(false) {} - ~NavigationObserver() override {} - - void DocumentOnLoadCompletedInMainFrame() override { - browser_test_->FinishAsynchronousTest(); - } - - void DidFinishNavigation(bool success) override { - navigation_succeeded_ = success; - } +IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, Navigation) { + EXPECT_TRUE(embedded_test_server()->Start()); + HeadlessWebContents* web_contents = browser()->CreateWebContents( + embedded_test_server()->GetURL("/hello.html"), gfx::Size(800, 600)); + EXPECT_TRUE(WaitForLoad(web_contents)); - bool navigation_succeeded() const { return navigation_succeeded_; } + std::vector<HeadlessWebContents*> all_web_contents = + browser()->GetAllWebContents(); - private: - HeadlessWebContentsTest* browser_test_; // Not owned. - bool navigation_succeeded_; -}; + EXPECT_EQ(static_cast<size_t>(1), all_web_contents.size()); + EXPECT_EQ(web_contents, all_web_contents[0]); +} -IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, Navigation) { +IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, WindowOpen) { EXPECT_TRUE(embedded_test_server()->Start()); - std::unique_ptr<HeadlessWebContents> web_contents = - browser()->CreateWebContents( - embedded_test_server()->GetURL("/hello.html"), gfx::Size(800, 600)); - NavigationObserver observer(this); - web_contents->AddObserver(&observer); - RunAsynchronousTest(); + HeadlessWebContents* web_contents = browser()->CreateWebContents( + embedded_test_server()->GetURL("/window_open.html"), gfx::Size(800, 600)); + EXPECT_TRUE(WaitForLoad(web_contents)); + + std::vector<HeadlessWebContents*> all_web_contents = + browser()->GetAllWebContents(); - EXPECT_TRUE(observer.navigation_succeeded()); - web_contents->RemoveObserver(&observer); + EXPECT_EQ(static_cast<size_t>(2), all_web_contents.size()); } } // namespace headless diff --git a/chromium/headless/public/domains/README.md b/chromium/headless/public/domains/README.md new file mode 100644 index 00000000000..a3f8898c5b6 --- /dev/null +++ b/chromium/headless/public/domains/README.md @@ -0,0 +1,3 @@ +The client API domain classes are autogenerated. You can find them under the +out-directory, e.g., out/Debug/gen/headless/public/domains, or in +[Code Search](https://code.google.com/p/chromium/codesearch#search/&q=f%3Agen/headless/public/domains&sq=package:chromium&type=cs). diff --git a/chromium/headless/public/domains/types_unittest.cc b/chromium/headless/public/domains/types_unittest.cc new file mode 100644 index 00000000000..c665d95b225 --- /dev/null +++ b/chromium/headless/public/domains/types_unittest.cc @@ -0,0 +1,200 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/json/json_reader.h" +#include "headless/public/domains/types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace headless { + +TEST(TypesTest, IntegerProperty) { + std::unique_ptr<accessibility::GetAXNodeParams> object( + accessibility::GetAXNodeParams::Builder().SetNodeId(123).Build()); + EXPECT_TRUE(object); + EXPECT_EQ(123, object->GetNodeId()); + + std::unique_ptr<accessibility::GetAXNodeParams> clone(object->Clone()); + EXPECT_TRUE(clone); + EXPECT_EQ(123, clone->GetNodeId()); +} + +TEST(TypesTest, IntegerPropertyParseError) { + const char* json = "{\"nodeId\": \"foo\"}"; + std::unique_ptr<base::Value> object = base::JSONReader::Read(json); + EXPECT_TRUE(object); + + ErrorReporter errors; + EXPECT_FALSE(accessibility::GetAXNodeParams::Parse(*object, &errors)); + EXPECT_TRUE(errors.HasErrors()); +} + +TEST(TypesTest, BooleanProperty) { + std::unique_ptr<memory::SetPressureNotificationsSuppressedParams> object( + memory::SetPressureNotificationsSuppressedParams::Builder() + .SetSuppressed(true) + .Build()); + EXPECT_TRUE(object); + EXPECT_TRUE(object->GetSuppressed()); + + std::unique_ptr<memory::SetPressureNotificationsSuppressedParams> clone( + object->Clone()); + EXPECT_TRUE(clone); + EXPECT_TRUE(clone->GetSuppressed()); +} + +TEST(TypesTest, BooleanPropertyParseError) { + const char* json = "{\"suppressed\": \"foo\"}"; + std::unique_ptr<base::Value> object = base::JSONReader::Read(json); + EXPECT_TRUE(object); + + ErrorReporter errors; + EXPECT_FALSE(memory::SetPressureNotificationsSuppressedParams::Parse( + *object, &errors)); + EXPECT_TRUE(errors.HasErrors()); +} + +TEST(TypesTest, DoubleProperty) { + std::unique_ptr<page::SetGeolocationOverrideParams> object( + page::SetGeolocationOverrideParams::Builder().SetLatitude(3.14).Build()); + EXPECT_TRUE(object); + EXPECT_EQ(3.14, object->GetLatitude()); + + std::unique_ptr<page::SetGeolocationOverrideParams> clone(object->Clone()); + EXPECT_TRUE(clone); + EXPECT_EQ(3.14, clone->GetLatitude()); +} + +TEST(TypesTest, DoublePropertyParseError) { + const char* json = "{\"latitude\": \"foo\"}"; + std::unique_ptr<base::Value> object = base::JSONReader::Read(json); + EXPECT_TRUE(object); + + ErrorReporter errors; + EXPECT_FALSE(page::SetGeolocationOverrideParams::Parse(*object, &errors)); + EXPECT_TRUE(errors.HasErrors()); +} + +TEST(TypesTest, StringProperty) { + std::unique_ptr<page::NavigateParams> object( + page::NavigateParams::Builder().SetUrl("url").Build()); + EXPECT_TRUE(object); + EXPECT_EQ("url", object->GetUrl()); + + std::unique_ptr<page::NavigateParams> clone(object->Clone()); + EXPECT_TRUE(clone); + EXPECT_EQ("url", clone->GetUrl()); +} + +TEST(TypesTest, StringPropertyParseError) { + const char* json = "{\"url\": false}"; + std::unique_ptr<base::Value> object = base::JSONReader::Read(json); + EXPECT_TRUE(object); + + ErrorReporter errors; + EXPECT_FALSE(page::NavigateParams::Parse(*object, &errors)); + EXPECT_TRUE(errors.HasErrors()); +} + +TEST(TypesTest, EnumProperty) { + std::unique_ptr<runtime::RemoteObject> object( + runtime::RemoteObject::Builder() + .SetType(runtime::RemoteObjectType::UNDEFINED) + .Build()); + EXPECT_TRUE(object); + EXPECT_EQ(runtime::RemoteObjectType::UNDEFINED, object->GetType()); + + std::unique_ptr<runtime::RemoteObject> clone(object->Clone()); + EXPECT_TRUE(clone); + EXPECT_EQ(runtime::RemoteObjectType::UNDEFINED, clone->GetType()); +} + +TEST(TypesTest, EnumPropertyParseError) { + const char* json = "{\"type\": false}"; + std::unique_ptr<base::Value> object = base::JSONReader::Read(json); + EXPECT_TRUE(object); + + ErrorReporter errors; + EXPECT_FALSE(runtime::RemoteObject::Parse(*object, &errors)); + EXPECT_TRUE(errors.HasErrors()); +} + +TEST(TypesTest, ArrayProperty) { + std::vector<int> values; + values.push_back(1); + values.push_back(2); + values.push_back(3); + + std::unique_ptr<dom::QuerySelectorAllResult> object( + dom::QuerySelectorAllResult::Builder().SetNodeIds(values).Build()); + EXPECT_TRUE(object); + EXPECT_EQ(3u, object->GetNodeIds()->size()); + EXPECT_EQ(1, object->GetNodeIds()->at(0)); + EXPECT_EQ(2, object->GetNodeIds()->at(1)); + EXPECT_EQ(3, object->GetNodeIds()->at(2)); + + std::unique_ptr<dom::QuerySelectorAllResult> clone(object->Clone()); + EXPECT_TRUE(clone); + EXPECT_EQ(3u, clone->GetNodeIds()->size()); + EXPECT_EQ(1, clone->GetNodeIds()->at(0)); + EXPECT_EQ(2, clone->GetNodeIds()->at(1)); + EXPECT_EQ(3, clone->GetNodeIds()->at(2)); +} + +TEST(TypesTest, ArrayPropertyParseError) { + const char* json = "{\"nodeIds\": true}"; + std::unique_ptr<base::Value> object = base::JSONReader::Read(json); + EXPECT_TRUE(object); + + ErrorReporter errors; + EXPECT_FALSE(dom::QuerySelectorAllResult::Parse(*object, &errors)); + EXPECT_TRUE(errors.HasErrors()); +} + +TEST(TypesTest, ObjectProperty) { + std::unique_ptr<runtime::RemoteObject> subobject( + runtime::RemoteObject::Builder() + .SetType(runtime::RemoteObjectType::SYMBOL) + .Build()); + std::unique_ptr<runtime::EvaluateResult> object( + runtime::EvaluateResult::Builder() + .SetResult(std::move(subobject)) + .Build()); + EXPECT_TRUE(object); + EXPECT_EQ(runtime::RemoteObjectType::SYMBOL, object->GetResult()->GetType()); + + std::unique_ptr<runtime::EvaluateResult> clone(object->Clone()); + EXPECT_TRUE(clone); + EXPECT_EQ(runtime::RemoteObjectType::SYMBOL, clone->GetResult()->GetType()); +} + +TEST(TypesTest, ObjectPropertyParseError) { + const char* json = "{\"result\": 42}"; + std::unique_ptr<base::Value> object = base::JSONReader::Read(json); + EXPECT_TRUE(object); + + ErrorReporter errors; + EXPECT_FALSE(runtime::EvaluateResult::Parse(*object, &errors)); + EXPECT_TRUE(errors.HasErrors()); +} + +TEST(TypesTest, AnyProperty) { + std::unique_ptr<base::Value> value(new base::FundamentalValue(123)); + std::unique_ptr<accessibility::AXValue> object( + accessibility::AXValue::Builder() + .SetType(accessibility::AXValueType::INTEGER) + .SetValue(std::move(value)) + .Build()); + EXPECT_TRUE(object); + EXPECT_EQ(base::Value::TYPE_INTEGER, object->GetValue()->GetType()); + + std::unique_ptr<accessibility::AXValue> clone(object->Clone()); + EXPECT_TRUE(clone); + EXPECT_EQ(base::Value::TYPE_INTEGER, clone->GetValue()->GetType()); + + int clone_value; + EXPECT_TRUE(clone->GetValue()->GetAsInteger(&clone_value)); + EXPECT_EQ(123, clone_value); +} + +} // namespace headless diff --git a/chromium/headless/public/headless_browser.cc b/chromium/headless/public/headless_browser.cc index b66efa40724..6ff0a0c35ff 100644 --- a/chromium/headless/public/headless_browser.cc +++ b/chromium/headless/public/headless_browser.cc @@ -20,7 +20,8 @@ Options::Options(int argc, const char** argv) : argc(argc), argv(argv), user_agent(content::BuildUserAgentFromProduct(kProductName)), - message_pump(nullptr) {} + message_pump(nullptr), + single_process_mode(false) {} Options::Options(const Options& other) = default; @@ -52,6 +53,16 @@ Builder& Builder::SetProxyServer(const net::HostPortPair& proxy_server) { return *this; } +Builder& Builder::SetHostResolverRules(const std::string& host_resolver_rules) { + options_.host_resolver_rules = host_resolver_rules; + return *this; +} + +Builder& Builder::SetSingleProcessMode(bool single_process_mode) { + options_.single_process_mode = single_process_mode; + return *this; +} + Options Builder::Build() { return options_; } diff --git a/chromium/headless/public/headless_browser.h b/chromium/headless/public/headless_browser.h index 011017471e1..de5f89bd3ef 100644 --- a/chromium/headless/public/headless_browser.h +++ b/chromium/headless/public/headless_browser.h @@ -37,9 +37,13 @@ class HEADLESS_EXPORT HeadlessBrowser { // Create a new browser tab which navigates to |initial_url|. |size| is in // physical pixels. - virtual std::unique_ptr<HeadlessWebContents> CreateWebContents( - const GURL& initial_url, - const gfx::Size& size) = 0; + // We require the user to pass an initial URL to ensure that the renderer + // gets initialized and eventually becomes ready to be inspected. See + // HeadlessWebContents::Observer::DevToolsTargetReady. + virtual HeadlessWebContents* CreateWebContents(const GURL& initial_url, + const gfx::Size& size) = 0; + + virtual std::vector<HeadlessWebContents*> GetAllWebContents() = 0; // Returns a task runner for submitting work to the browser main thread. virtual scoped_refptr<base::SingleThreadTaskRunner> BrowserMainThread() @@ -82,6 +86,15 @@ struct HeadlessBrowser::Options { // Optional message pump that overrides the default. Must outlive the browser. base::MessagePump* message_pump; + // Comma-separated list of rules that control how hostnames are mapped. See + // chrome::switches::kHostRules for a description for the format. + std::string host_resolver_rules; + + // Run the browser in single process mode instead of using separate renderer + // processes as per default. Note that this also disables any sandboxing of + // web content, which can be a security risk. + bool single_process_mode; + private: Options(int argc, const char** argv); }; @@ -96,6 +109,8 @@ class HeadlessBrowser::Options::Builder { Builder& EnableDevToolsServer(const net::IPEndPoint& endpoint); Builder& SetMessagePump(base::MessagePump* message_pump); Builder& SetProxyServer(const net::HostPortPair& proxy_server); + Builder& SetHostResolverRules(const std::string& host_resolver_rules); + Builder& SetSingleProcessMode(bool single_process_mode); Options Build(); diff --git a/chromium/headless/public/headless_devtools_client.h b/chromium/headless/public/headless_devtools_client.h new file mode 100644 index 00000000000..a71ce657200 --- /dev/null +++ b/chromium/headless/public/headless_devtools_client.h @@ -0,0 +1,157 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef HEADLESS_PUBLIC_HEADLESS_DEVTOOLS_CLIENT_H_ +#define HEADLESS_PUBLIC_HEADLESS_DEVTOOLS_CLIENT_H_ + +#include <memory> + +#include "base/macros.h" +#include "headless/public/headless_export.h" + +namespace headless { + +namespace accessibility { +class Domain; +} +namespace animation { +class Domain; +} +namespace application_cache { +class Domain; +} +namespace cache_storage { +class Domain; +} +namespace console { +class Domain; +} +namespace css { +class Domain; +} +namespace database { +class Domain; +} +namespace debugger { +class Domain; +} +namespace device_orientation { +class Domain; +} +namespace dom { +class Domain; +} +namespace dom_debugger { +class Domain; +} +namespace dom_storage { +class Domain; +} +namespace emulation { +class Domain; +} +namespace heap_profiler { +class Domain; +} +namespace indexeddb { +class Domain; +} +namespace input { +class Domain; +} +namespace inspector { +class Domain; +} +namespace io { +class Domain; +} +namespace layer_tree { +class Domain; +} +namespace memory { +class Domain; +} +namespace network { +class Domain; +} +namespace page { +class Domain; +} +namespace profiler { +class Domain; +} +namespace rendering { +class Domain; +} +namespace runtime { +class Domain; +} +namespace security { +class Domain; +} +namespace service_worker { +class Domain; +} +namespace tracing { +class Domain; +} +namespace worker { +class Domain; +} + +// An interface for controlling and receiving events from a devtools target. +class HEADLESS_EXPORT HeadlessDevToolsClient { + public: + virtual ~HeadlessDevToolsClient() {} + + static std::unique_ptr<HeadlessDevToolsClient> Create(); + + // DevTools commands are split into domains which corresponds to the getters + // below. Each domain can be used to send commands and to subscribe to events. + // + // See http://chromedevtools.github.io/debugger-protocol-viewer/ for + // the capabilities of each domain. + virtual accessibility::Domain* GetAccessibility() = 0; + virtual animation::Domain* GetAnimation() = 0; + virtual application_cache::Domain* GetApplicationCache() = 0; + virtual cache_storage::Domain* GetCacheStorage() = 0; + virtual console::Domain* GetConsole() = 0; + virtual css::Domain* GetCSS() = 0; + virtual database::Domain* GetDatabase() = 0; + virtual debugger::Domain* GetDebugger() = 0; + virtual device_orientation::Domain* GetDeviceOrientation() = 0; + virtual dom::Domain* GetDOM() = 0; + virtual dom_debugger::Domain* GetDOMDebugger() = 0; + virtual dom_storage::Domain* GetDOMStorage() = 0; + virtual emulation::Domain* GetEmulation() = 0; + virtual heap_profiler::Domain* GetHeapProfiler() = 0; + virtual indexeddb::Domain* GetIndexedDB() = 0; + virtual input::Domain* GetInput() = 0; + virtual inspector::Domain* GetInspector() = 0; + virtual io::Domain* GetIO() = 0; + virtual layer_tree::Domain* GetLayerTree() = 0; + virtual memory::Domain* GetMemory() = 0; + virtual network::Domain* GetNetwork() = 0; + virtual page::Domain* GetPage() = 0; + virtual profiler::Domain* GetProfiler() = 0; + virtual rendering::Domain* GetRendering() = 0; + virtual runtime::Domain* GetRuntime() = 0; + virtual security::Domain* GetSecurity() = 0; + virtual service_worker::Domain* GetServiceWorker() = 0; + virtual tracing::Domain* GetTracing() = 0; + virtual worker::Domain* GetWorker() = 0; + + // TODO(skyostil): Add notification for disconnection. + + private: + friend class HeadlessDevToolsClientImpl; + + HeadlessDevToolsClient() {} + + DISALLOW_COPY_AND_ASSIGN(HeadlessDevToolsClient); +}; + +} // namespace headless + +#endif // HEADLESS_PUBLIC_HEADLESS_DEVTOOLS_CLIENT_H_ diff --git a/chromium/headless/public/headless_devtools_target.h b/chromium/headless/public/headless_devtools_target.h new file mode 100644 index 00000000000..941fe6f51e2 --- /dev/null +++ b/chromium/headless/public/headless_devtools_target.h @@ -0,0 +1,37 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef HEADLESS_PUBLIC_HEADLESS_DEVTOOLS_TARGET_H_ +#define HEADLESS_PUBLIC_HEADLESS_DEVTOOLS_TARGET_H_ + +#include "base/macros.h" +#include "headless/public/headless_export.h" + +namespace headless { +class HeadlessDevToolsClient; + +// A target which can be controlled and inspected using DevTools. +class HEADLESS_EXPORT HeadlessDevToolsTarget { + public: + HeadlessDevToolsTarget() {} + virtual ~HeadlessDevToolsTarget() {} + + // Attach or detach a client to this target. A client must be attached in + // order to send commands or receive notifications from the target. + // + // A single client may be attached to at most one target at a time. Note that + // currently also only one client may be attached to a single target at a + // time. + // + // |client| must outlive this target. + virtual void AttachClient(HeadlessDevToolsClient* client) = 0; + virtual void DetachClient(HeadlessDevToolsClient* client) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(HeadlessDevToolsTarget); +}; + +} // namespace headless + +#endif // HEADLESS_PUBLIC_HEADLESS_DEVTOOLS_TARGET_H_ diff --git a/chromium/headless/public/headless_web_contents.h b/chromium/headless/public/headless_web_contents.h index 04ac953001d..27a4e8f4b12 100644 --- a/chromium/headless/public/headless_web_contents.h +++ b/chromium/headless/public/headless_web_contents.h @@ -7,11 +7,11 @@ #include "base/callback.h" #include "base/macros.h" -#include "base/memory/scoped_ptr.h" #include "headless/public/headless_export.h" #include "url/gurl.h" namespace headless { +class HeadlessDevToolsTarget; // Class representing contents of a browser tab. Should be accessed from browser // main thread. @@ -19,16 +19,15 @@ class HEADLESS_EXPORT HeadlessWebContents { public: virtual ~HeadlessWebContents() {} - // TODO(skyostil): Replace this with an equivalent client API. class Observer { public: // All the following notifications will be called on browser main thread. - virtual void DocumentOnLoadCompletedInMainFrame(){}; - virtual void DidFinishNavigation(bool success){}; - // After this event, this HeadlessWebContents instance is ready to be - // controlled using a DevTools client. - virtual void WebContentsReady(){}; + // Indicates that this HeadlessWebContents instance is now ready to be + // inspected using a HeadlessDevToolsClient. + // + // TODO(altimin): Support this event for pages that aren't created by us. + virtual void DevToolsTargetReady() {} protected: Observer() {} @@ -43,6 +42,14 @@ class HEADLESS_EXPORT HeadlessWebContents { virtual void AddObserver(Observer* observer) = 0; virtual void RemoveObserver(Observer* observer) = 0; + // Return a DevTools target corresponding to this tab. Note that this method + // won't return a valid value until Observer::DevToolsTargetReady has been + // signaled. + virtual HeadlessDevToolsTarget* GetDevToolsTarget() = 0; + + // Close this page. |HeadlessWebContents| object will be destroyed. + virtual void Close() = 0; + private: friend class HeadlessWebContentsImpl; HeadlessWebContents() {} diff --git a/chromium/headless/public/internal/message_dispatcher.h b/chromium/headless/public/internal/message_dispatcher.h new file mode 100644 index 00000000000..ea939782b84 --- /dev/null +++ b/chromium/headless/public/internal/message_dispatcher.h @@ -0,0 +1,35 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef HEADLESS_PUBLIC_INTERNAL_MESSAGE_DISPATCHER_H_ +#define HEADLESS_PUBLIC_INTERNAL_MESSAGE_DISPATCHER_H_ + +namespace headless { +namespace internal { + +// An internal interface for sending DevTools messages from the domain agents. +class MessageDispatcher { + public: + virtual void SendMessage( + const char* method, + std::unique_ptr<base::Value> params, + base::Callback<void(const base::Value&)> callback) = 0; + virtual void SendMessage(const char* method, + std::unique_ptr<base::Value> params, + base::Callback<void()> callback) = 0; + virtual void SendMessage( + const char* method, + base::Callback<void(const base::Value&)> callback) = 0; + virtual void SendMessage(const char* method, + base::Callback<void()> callback) = 0; + + virtual void RegisterEventHandler( + const char* method, + base::Callback<void(const base::Value&)> callback) = 0; +}; + +} // namespace internal +} // namespace headless + +#endif // HEADLESS_PUBLIC_INTERNAL_MESSAGE_DISPATCHER_H_ diff --git a/chromium/headless/public/internal/value_conversions.h b/chromium/headless/public/internal/value_conversions.h new file mode 100644 index 00000000000..17c6eda3f22 --- /dev/null +++ b/chromium/headless/public/internal/value_conversions.h @@ -0,0 +1,163 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef HEADLESS_PUBLIC_INTERNAL_VALUE_CONVERSIONS_H_ +#define HEADLESS_PUBLIC_INTERNAL_VALUE_CONVERSIONS_H_ + +#include <memory> + +#include "base/memory/ptr_util.h" +#include "headless/public/util/error_reporter.h" + +namespace headless { +namespace internal { + +// Generic conversion from a type to a base::Value. Implemented in +// type_conversions.h after all type-specific ToValueImpls have been defined. +template <typename T> +std::unique_ptr<base::Value> ToValue(const T& value); + +// Generic conversion from a base::Value to a type. Note that this generic +// variant is never defined. Instead, we declare a specific template +// specialization for all the used types. +template <typename T> +struct FromValue { + static std::unique_ptr<T> Parse(const base::Value& value, + ErrorReporter* errors); +}; + +// ToValueImpl is a helper used by the ToValue template for dispatching into +// type-specific serializers. It uses a dummy |T*| argument as a way to +// partially specialize vector types. +template <typename T> +std::unique_ptr<base::Value> ToValueImpl(int value, T*) { + return base::WrapUnique(new base::FundamentalValue(value)); +} + +template <typename T> +std::unique_ptr<base::Value> ToValueImpl(double value, T*) { + return base::WrapUnique(new base::FundamentalValue(value)); +} + +template <typename T> +std::unique_ptr<base::Value> ToValueImpl(bool value, T*) { + return base::WrapUnique(new base::FundamentalValue(value)); +} + +template <typename T> +std::unique_ptr<base::Value> ToValueImpl(const std::string& value, T*) { + return base::WrapUnique(new base::StringValue(value)); +} + +template <typename T> +std::unique_ptr<base::Value> ToValueImpl(const base::Value& value, T*) { + return value.CreateDeepCopy(); +} + +template <typename T> +std::unique_ptr<base::Value> ToValueImpl(const std::vector<T>& vector, + const std::vector<T>*) { + std::unique_ptr<base::ListValue> result(new base::ListValue()); + for (const auto& it : vector) + result->Append(ToValue(it)); + return std::move(result); +} + +template <typename T> +std::unique_ptr<base::Value> ToValueImpl(const std::unique_ptr<T>& value, + std::unique_ptr<T>*) { + return ToValue(value.get()); +} + +// FromValue specializations for basic types. +template <> +struct FromValue<bool> { + static bool Parse(const base::Value& value, ErrorReporter* errors) { + bool result = false; + if (!value.GetAsBoolean(&result)) + errors->AddError("boolean value expected"); + return result; + } +}; + +template <> +struct FromValue<int> { + static int Parse(const base::Value& value, ErrorReporter* errors) { + int result = 0; + if (!value.GetAsInteger(&result)) + errors->AddError("integer value expected"); + return result; + } +}; + +template <> +struct FromValue<double> { + static double Parse(const base::Value& value, ErrorReporter* errors) { + double result = 0; + if (!value.GetAsDouble(&result)) + errors->AddError("double value expected"); + return result; + } +}; + +template <> +struct FromValue<std::string> { + static std::string Parse(const base::Value& value, ErrorReporter* errors) { + std::string result; + if (!value.GetAsString(&result)) + errors->AddError("string value expected"); + return result; + } +}; + +template <> +struct FromValue<base::DictionaryValue> { + static std::unique_ptr<base::DictionaryValue> Parse(const base::Value& value, + ErrorReporter* errors) { + const base::DictionaryValue* result; + if (!value.GetAsDictionary(&result)) { + errors->AddError("dictionary value expected"); + return nullptr; + } + return result->CreateDeepCopy(); + } +}; + +template <> +struct FromValue<base::Value> { + static std::unique_ptr<base::Value> Parse(const base::Value& value, + ErrorReporter* errors) { + return value.CreateDeepCopy(); + } +}; + +template <typename T> +struct FromValue<std::unique_ptr<T>> { + static std::unique_ptr<T> Parse(const base::Value& value, + ErrorReporter* errors) { + return FromValue<T>::Parse(value, errors); + } +}; + +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)) { + errors->AddError("list value expected"); + return result; + } + errors->Push(); + for (const auto& item : *list) + result.push_back(FromValue<T>::Parse(*item, errors)); + errors->Pop(); + return result; + } +}; + +} // namespace internal +} // namespace headless + +#endif // HEADLESS_PUBLIC_INTERNAL_VALUE_CONVERSIONS_H_ diff --git a/chromium/headless/public/util/error_reporter.cc b/chromium/headless/public/util/error_reporter.cc new file mode 100644 index 00000000000..54c0758b71d --- /dev/null +++ b/chromium/headless/public/util/error_reporter.cc @@ -0,0 +1,51 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "headless/public/util/error_reporter.h" + +#include <sstream> + +#include "base/logging.h" + +namespace headless { + +ErrorReporter::ErrorReporter() {} + +ErrorReporter::~ErrorReporter() {} + +void ErrorReporter::Push() { + path_.push_back(nullptr); +} + +void ErrorReporter::Pop() { + path_.pop_back(); +} + +void ErrorReporter::SetName(const char* name) { + DCHECK(!path_.empty()); + path_[path_.size() - 1] = name; +} + +void ErrorReporter::AddError(base::StringPiece description) { + std::stringstream error; + for (size_t i = 0; i < path_.size(); i++) { + if (!path_[i]) { + DCHECK_EQ(i + 1, path_.size()); + break; + } + if (i) + error << '.'; + error << path_[i]; + } + if (error.tellp()) + error << ": "; + error << description; + errors_.push_back(error.str()); +} + +bool ErrorReporter::HasErrors() const { + return !errors_.empty(); +} + +} // namespace headless diff --git a/chromium/headless/public/util/error_reporter.h b/chromium/headless/public/util/error_reporter.h new file mode 100644 index 00000000000..d7eb5e96d21 --- /dev/null +++ b/chromium/headless/public/util/error_reporter.h @@ -0,0 +1,48 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef HEADLESS_PUBLIC_UTIL_ERROR_REPORTER_H_ +#define HEADLESS_PUBLIC_UTIL_ERROR_REPORTER_H_ + +#include <string> +#include <vector> + +#include "base/strings/string_piece.h" +#include "headless/public/headless_export.h" + +namespace headless { + +// Tracks errors which are encountered while parsing client API types. +class HEADLESS_EXPORT ErrorReporter { + public: + ErrorReporter(); + ~ErrorReporter(); + + // Enter a new nested parsing context. It will initially have a null name. + void Push(); + + // Leave the current parsing context, returning to the previous one. + void Pop(); + + // Set the name of the current parsing context. |name| must be a string with + // application lifetime. + void SetName(const char* name); + + // Report an error in the current parsing context. + void AddError(base::StringPiece description); + + // Returns true if any errors have been reported so far. + bool HasErrors() const; + + // Returns a list of reported errors. + const std::vector<std::string>& errors() const { return errors_; } + + private: + std::vector<const char*> path_; + std::vector<std::string> errors_; +}; + +} // namespace headless + +#endif // HEADLESS_PUBLIC_UTIL_ERROR_REPORTER_H_ diff --git a/chromium/headless/public/util/error_reporter_unittest.cc b/chromium/headless/public/util/error_reporter_unittest.cc new file mode 100644 index 00000000000..f23ced9ef29 --- /dev/null +++ b/chromium/headless/public/util/error_reporter_unittest.cc @@ -0,0 +1,52 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "headless/public/util/error_reporter.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace headless { + +TEST(ErrorReporterTest, NoErrors) { + ErrorReporter reporter; + EXPECT_FALSE(reporter.HasErrors()); + EXPECT_TRUE(reporter.errors().empty()); +} + +TEST(ErrorReporterTest, TopLevelErrors) { + ErrorReporter reporter; + reporter.AddError("instructions unclear"); + reporter.AddError("head stuck in std::unordered_map"); + EXPECT_TRUE(reporter.HasErrors()); + EXPECT_EQ(2u, reporter.errors().size()); + EXPECT_EQ("instructions unclear", reporter.errors()[0]); + EXPECT_EQ("head stuck in std::unordered_map", reporter.errors()[1]); +} + +TEST(ErrorReporterTest, UnnamedContext) { + ErrorReporter reporter; + reporter.Push(); + reporter.AddError("lp0 is on fire"); + reporter.Pop(); + EXPECT_TRUE(reporter.HasErrors()); + EXPECT_EQ(1u, reporter.errors().size()); + EXPECT_EQ("lp0 is on fire", reporter.errors()[0]); +} + +TEST(ErrorReporterTest, NestedContexts) { + ErrorReporter reporter; + reporter.Push(); + reporter.SetName("ship"); + reporter.Push(); + reporter.SetName("front"); + reporter.AddError("fell off"); + reporter.Pop(); + reporter.AddError("uh oh"); + reporter.Pop(); + EXPECT_TRUE(reporter.HasErrors()); + EXPECT_EQ(2u, reporter.errors().size()); + EXPECT_EQ("ship.front: fell off", reporter.errors()[0]); + EXPECT_EQ("ship: uh oh", reporter.errors()[1]); +} + +} // namespace headless diff --git a/chromium/headless/public/util/maybe.h b/chromium/headless/public/util/maybe.h deleted file mode 100644 index 18540a3634f..00000000000 --- a/chromium/headless/public/util/maybe.h +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef HEADLESS_PUBLIC_UTIL_MAYBE_H_ -#define HEADLESS_PUBLIC_UTIL_MAYBE_H_ - -#include <algorithm> - -#include "base/logging.h" -#include "base/macros.h" - -namespace headless { - -// A simple Maybe which may or may not have a value. Based on v8::Maybe. -template <typename T> -class Maybe { - public: - Maybe() : has_value_(false) {} - - bool IsNothing() const { return !has_value_; } - bool IsJust() const { return has_value_; } - - // Will crash if the Maybe<> is nothing. - T& FromJust() { - DCHECK(IsJust()); - return value_; - } - const T& FromJust() const { - DCHECK(IsJust()); - return value_; - } - - T FromMaybe(const T& default_value) const { - return has_value_ ? value_ : default_value; - } - - bool operator==(const Maybe& other) const { - return (IsJust() == other.IsJust()) && - (!IsJust() || FromJust() == other.FromJust()); - } - - bool operator!=(const Maybe& other) const { return !operator==(other); } - - Maybe& operator=(Maybe&& other) { - has_value_ = other.has_value_; - value_ = std::move(other.value_); - return *this; - } - - Maybe& operator=(const Maybe& other) { - has_value_ = other.has_value_; - value_ = other.value_; - return *this; - } - - Maybe(const Maybe& other) = default; - Maybe(Maybe&& other) = default; - - private: - template <class U> - friend Maybe<U> Nothing(); - template <class U> - friend Maybe<U> Just(const U& u); - template <class U> - friend Maybe<typename std::remove_reference<U>::type> Just(U&& u); - - explicit Maybe(const T& t) : has_value_(true), value_(t) {} - explicit Maybe(T&& t) : has_value_(true), value_(std::move(t)) {} - - bool has_value_; - T value_; -}; - -template <class T> -Maybe<T> Nothing() { - return Maybe<T>(); -} - -template <class T> -Maybe<T> Just(const T& t) { - return Maybe<T>(t); -} - -template <class T> -Maybe<typename std::remove_reference<T>::type> Just(T&& t) { - return Maybe<typename std::remove_reference<T>::type>(std::move(t)); -} - -} // namespace headless - -#endif // HEADLESS_PUBLIC_UTIL_MAYBE_H_ diff --git a/chromium/headless/public/util/maybe_unittest.cc b/chromium/headless/public/util/maybe_unittest.cc deleted file mode 100644 index fb926961df5..00000000000 --- a/chromium/headless/public/util/maybe_unittest.cc +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "headless/public/util/maybe.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace headless { -namespace { - -class MoveOnlyType { - public: - MoveOnlyType() {} - MoveOnlyType(MoveOnlyType&& other) {} - - void operator=(MoveOnlyType&& other) {} - - private: - DISALLOW_COPY_AND_ASSIGN(MoveOnlyType); -}; - -} // namespace - -TEST(MaybeTest, Nothing) { - Maybe<int> maybe; - EXPECT_TRUE(maybe.IsNothing()); - EXPECT_FALSE(maybe.IsJust()); -} - -TEST(MaybeTest, Just) { - Maybe<int> maybe = Just(1); - EXPECT_FALSE(maybe.IsNothing()); - EXPECT_TRUE(maybe.IsJust()); - EXPECT_EQ(1, maybe.FromJust()); - - const Maybe<int> const_maybe = Just(2); - EXPECT_EQ(2, const_maybe.FromJust()); -} - -TEST(MaybeTest, Equality) { - Maybe<int> a; - Maybe<int> b; - EXPECT_EQ(a, b); - EXPECT_EQ(b, a); - - a = Just(1); - EXPECT_NE(a, b); - EXPECT_NE(b, a); - - b = Just(2); - EXPECT_NE(a, b); - EXPECT_NE(b, a); - - b = Just(1); - EXPECT_EQ(a, b); - EXPECT_EQ(b, a); -} - -TEST(MaybeTest, Assignment) { - Maybe<int> a = Just(1); - Maybe<int> b = Nothing<int>(); - EXPECT_NE(a, b); - - b = a; - EXPECT_EQ(a, b); -} - -TEST(MaybeTest, MoveOnlyType) { - MoveOnlyType value; - Maybe<MoveOnlyType> a = Just(std::move(value)); - EXPECT_TRUE(a.IsJust()); - - Maybe<MoveOnlyType> b = Just(MoveOnlyType()); - EXPECT_TRUE(b.IsJust()); - - Maybe<MoveOnlyType> c = Nothing<MoveOnlyType>(); - c = std::move(a); - EXPECT_TRUE(c.IsJust()); - - MoveOnlyType d = std::move(b.FromJust()); -} - -} // namespace headless |