#!/usr/bin/env python # Copyright 2014 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 sys import string import json blink_protocol_path = sys.argv[1] browser_protocol_path = sys.argv[2] output_cc_path = sys.argv[3] output_h_path = sys.argv[4] header = """\ // Copyright 2014 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. // THIS FILE IS AUTOGENERATED. DO NOT EDIT. // Generated by // content/public/browser/devtools_protocol_handler_generator.py from // third_party/WebKit/Source/devtools/protocol.json and // content/browser/devtools/browser_protocol.json """ template_h = string.Template(header + """\ #ifndef CONTENT_BROWSER_DEVTOOLS_PROTOCOL_DEVTOOLS_PROTOCOL_DISPATCHER_H_ #define CONTENT_BROWSER_DEVTOOLS_PROTOCOL_DEVTOOLS_PROTOCOL_DISPATCHER_H_ #include "content/browser/devtools/protocol/devtools_protocol_client.h" namespace content { class DevToolsProtocolDispatcher; namespace devtools { extern const char kProtocolVersion[]; bool IsSupportedProtocolVersion(const std::string& version); template base::Value* CreateValue(const T& param) { return new base::FundamentalValue(param); } template base::Value* CreateValue(scoped_ptr& param) { return param.release(); } template base::Value* CreateValue(scoped_refptr param) { return param->ToValue().release(); } template base::Value* CreateValue(const std::vector param) { base::ListValue* result = new base::ListValue(); for (auto& item : param) { result->Append(CreateValue(item)); } return result; } template<> base::Value* CreateValue(const std::string& param); ${types}\ } // namespace devtools class DevToolsProtocolDispatcher { public: using Notifier = DevToolsProtocolClient::RawMessageCallback; using CommandHandler = base::Callback)>; explicit DevToolsProtocolDispatcher(const Notifier& notifier); ~DevToolsProtocolDispatcher(); CommandHandler FindCommandHandler(const std::string& method); ${setters}\ private: using Response = DevToolsProtocolClient::Response; using CommandHandlers = std::map; ${methods}\ Notifier notifier_; DevToolsProtocolClient client_; CommandHandlers command_handlers_; ${fields}\ }; } // namespace content #endif // CONTENT_BROWSER_DEVTOOLS_PROTOCOL_DEVTOOLS_PROTOCOL_DISPATCHER_H_ """) tmpl_typedef = string.Template("""\ namespace ${domain} { typedef ${param_type} ${declared_name}; } // namespace ${domain} """) tmpl_struct = string.Template("""\ namespace ${domain} { template struct ${declared_name}Builder : base::RefCounted<${declared_name}Builder> { public: enum { kAllSet = 0, ${fields_enum}\ }; ${methods}\ static scoped_refptr<${declared_name}Builder> Create() { return new ${declared_name}Builder(); } scoped_ptr ToValue() { static_assert(MASK == kAllSet, "required properties missing"); return make_scoped_ptr(dict_->DeepCopy()); } private: friend struct ${declared_name}Builder<0>; ${declared_name}Builder() : dict_(new base::DictionaryValue()) { } template T* ThisAs() { static_assert(sizeof(*this) == sizeof(T), "cannot cast"); return reinterpret_cast(this); } scoped_ptr dict_; }; typedef ${declared_name}Builder<0> ${declared_name}; } // namespace ${domain} """) tmpl_builder_setter_req = string.Template("""\ scoped_refptr<${declared_name}Builder> set_${param}(${pass_type} ${param}) { static_assert(MASK & k${Param}, "already set"); dict_->Set("${proto_param}", CreateValue(${param})); return ThisAs<${declared_name}Builder>(); } """) tmpl_builder_setter_opt = string.Template("""\ scoped_refptr<${declared_name}Builder> set_${param}(${pass_type} ${param}) { dict_->Set("${proto_param}", CreateValue(${param})); return this; } """) tmpl_builder_enum = string.Template("""\ k${Param} = 1 << ${ordinal}, """) tmpl_builder_none_set = string.Template("""\ kNoneSet = ${all_fields} """) tmpl_named_enum = string.Template("""\ namespace ${domain} { ${values}\ } // namespace ${domain} """) tmpl_inline_enum = string.Template("""\ namespace ${domain} { namespace ${subdomain} { ${values}\ } // namespace ${subdomain} } // namespace ${domain} """) tmpl_enum_value = string.Template("""\ extern const char k${Enum}${Value}[]; """) tmpl_enum_value_def = string.Template("""\ const char k${Enum}${Value}[] = "${value}"; """) tmpl_handler = string.Template("""\ namespace ${domain} { class ${Domain}Handler; } // namespace domain """) tmpl_client = string.Template("""\ namespace ${domain} { class Client : public DevToolsProtocolClient { public: explicit Client(const RawMessageCallback& raw_message_callback); ~Client() override; ${methods}\ }; } // namespace ${domain} """) tmpl_event = string.Template("""\ void ${Command}( scoped_refptr<${Command}Params> params); """) tmpl_response = string.Template("""\ void Send${Command}Response( DevToolsCommandId command_id, scoped_refptr<${Command}Response> params); """) tmpl_setter = string.Template("""\ void Set${Domain}Handler( devtools::${domain}::${Domain}Handler* ${domain}_handler); """) tmpl_callback = string.Template("""\ bool On${Domain}${Command}( DevToolsCommandId command_id, scoped_ptr params); """) tmpl_field = string.Template("""\ devtools::${domain}::${Domain}Handler* ${domain}_handler_; """) template_cc = string.Template(header + """\ #include "content/browser/devtools/protocol/devtools_protocol_handler.h" #include "base/bind.h" #include "base/strings/string_number_conversions.h" ${includes}\ namespace content { DevToolsProtocolDispatcher::DevToolsProtocolDispatcher( const Notifier& notifier) : notifier_(notifier), client_(notifier), ${fields_init} { } DevToolsProtocolDispatcher::~DevToolsProtocolDispatcher() { } DevToolsProtocolDispatcher::CommandHandler DevToolsProtocolDispatcher::FindCommandHandler(const std::string& method) { CommandHandlers::iterator it = command_handlers_.find(method); return it == command_handlers_.end() ? CommandHandler() : it->second; } ${methods}\ namespace devtools { const char kProtocolVersion[] = "${major}.${minor}"; bool IsSupportedProtocolVersion(const std::string& version) { std::vector tokens; Tokenize(version, ".", &tokens); int major, minor; return tokens.size() == 2 && base::StringToInt(tokens[0], &major) && major == ${major} && base::StringToInt(tokens[1], &minor) && minor <= ${minor}; } template<> base::Value* CreateValue(const std::string& param) { return new base::StringValue(param); } ${types}\ } // namespace devtools } // namespace content """) tmpl_include = string.Template("""\ #include "content/browser/devtools/protocol/${domain}_handler.h" """) tmpl_field_init = string.Template("${domain}_handler_(nullptr)") tmpl_setter_impl = string.Template("""\ void DevToolsProtocolDispatcher::Set${Domain}Handler( devtools::${domain}::${Domain}Handler* ${domain}_handler) { DCHECK(!${domain}_handler_); ${domain}_handler_ = ${domain}_handler; ${initializations}\ } """) tmpl_register = string.Template("""\ command_handlers_["${Domain}.${command}"] = base::Bind( &DevToolsProtocolDispatcher::On${TargetDomain}${Command}, base::Unretained(this)); """) tmpl_init_client = string.Template("""\ ${domain}_handler_->SetClient(make_scoped_ptr( new devtools::${domain}::Client(notifier_))); """) tmpl_callback_impl = string.Template("""\ bool DevToolsProtocolDispatcher::On${Domain}${Command}( DevToolsCommandId command_id, scoped_ptr params) { ${prep}\ Response response = ${domain}_handler_->${Command}(${args}); scoped_ptr protocol_response; if (client_.SendError(command_id, response)) return true; if (response.IsFallThrough()) return false; scoped_ptr result(new base::DictionaryValue()); ${wrap}\ client_.SendSuccess(command_id, result.Pass()); return true; } """) tmpl_wrap = string.Template("""\ result->Set("${proto_param}", devtools::CreateValue(out_${param})); """) tmpl_callback_async_impl = string.Template("""\ bool DevToolsProtocolDispatcher::On${Domain}${Command}( DevToolsCommandId command_id, scoped_ptr params) { ${prep}\ Response response = ${domain}_handler_->${Command}(${args}); if (client_.SendError(command_id, response)) return true; return !response.IsFallThrough(); } """) tmpl_prep_req = string.Template("""\ ${raw_type} in_${param}${init}; if (!params || !params->Get${Type}("${proto_param}", &in_${param})) { client_.SendError(command_id, Response::InvalidParams("${proto_param}")); return true; } """) tmpl_prep_req_list = string.Template("""\ base::ListValue* list_${param} = nullptr; if (!params || !params->GetList("${proto_param}", &list_${param})) { client_.SendError(command_id, Response::InvalidParams("${proto_param}")); return true; } std::vector<${item_type}> in_${param}; for (base::ListValue::const_iterator it = list_${param}->begin(); it != list_${param}->end(); ++it) { ${item_raw_type} item; if (!(*it)->GetAs${ItemType}(&item)) { client_.SendError(command_id, Response::InvalidParams("${proto_param}")); return true; } in_${param}.push_back(${item_pass}); } """) tmpl_prep_opt = string.Template("""\ ${raw_type} in_${param}${init}; bool ${param}_found = params && params->Get${Type}( "${proto_param}", &in_${param}); """) tmpl_prep_output = string.Template("""\ ${param_type} out_${param}${init}; """) tmpl_arg_name = string.Template("in_${param}") tmpl_arg_req = string.Template("${param_pass}") tmpl_arg_opt = string.Template( "${param}_found ? ${param_pass} : nullptr") tmpl_object_pass = string.Template( "make_scoped_ptr(${name}->DeepCopy())") tmpl_client_impl = string.Template("""\ namespace ${domain} { Client::Client(const RawMessageCallback& raw_message_callback) : DevToolsProtocolClient(raw_message_callback) { } Client::~Client() { } ${methods}\ } // namespace ${domain} """) tmpl_event_impl = string.Template("""\ void Client::${Command}( scoped_refptr<${Command}Params> params) { SendNotification("${Domain}.${command}", params->ToValue().Pass()); } """) tmpl_response_impl = string.Template("""\ void Client::Send${Command}Response( DevToolsCommandId command_id, scoped_refptr<${Command}Response> params) { SendSuccess(command_id, params->ToValue().Pass()); } """) tmpl_typename = string.Template("devtools::${domain}::${declared_name}") def Capitalize(s): return s[:1].upper() + s[1:] def Uncamelcase(s): result = "" for i, c in enumerate(s): if c.isupper(): if (i > 0) and ((i < len(s)-1) and s[i+1].islower() or s[i-1].islower()): result += "_" result += c.lower() else: result += c return result types = {} blink_protocol = json.loads(open(blink_protocol_path, "r").read()) browser_protocol = json.loads(open(browser_protocol_path, "r").read()) type_decls = [] type_impls = [] handler_methods = [] handler_method_impls = [] domain_maps = [] redirects = {} all_domains = blink_protocol["domains"] + browser_protocol["domains"] for json_domain in all_domains: if "types" in json_domain: for json_type in json_domain["types"]: types["%s.%s" % (json_domain["domain"], json_type["id"])] = json_type def DeclareStruct(json_properties, mapping): methods = [] fields_enum = [] enum_items = [] req_fields_num = 0 for json_prop in json_properties: prop_map = mapping.copy() prop_map["proto_param"] = json_prop["name"] prop_map["param"] = Uncamelcase(json_prop["name"]) prop_map["Param"] = Capitalize(json_prop["name"]) prop_map["subdomain"] = Uncamelcase(prop_map["declared_name"]) del prop_map["declared_name"] ResolveType(json_prop, prop_map) prop_map["declared_name"] = mapping["declared_name"] if json_prop.get("optional"): methods.append(tmpl_builder_setter_opt.substitute(prop_map)) else: methods.append(tmpl_builder_setter_req.substitute(prop_map)) enum_items.append("k%s" % prop_map["Param"]); fields_enum.append(tmpl_builder_enum.substitute(prop_map, ordinal = req_fields_num)) req_fields_num += 1 all_fields = "kAllSet" if len(enum_items) > 0: all_fields = " | ".join(enum_items) fields_enum.append(tmpl_builder_none_set.substitute(mapping, all_fields = all_fields)) type_decls.append(tmpl_struct.substitute(mapping, methods = "\n".join(methods), fields_enum = "".join(fields_enum))) def DeclareEnum(json, mapping): values = [] value_defs = [] tmpl_enum = tmpl_inline_enum if "declared_name" in mapping: mapping["Enum"] = mapping["declared_name"] tmpl_enum = tmpl_named_enum else: mapping["Enum"] = Capitalize(mapping["proto_param"]) for enum_value in json["enum"]: values.append(tmpl_enum_value.substitute(mapping, Value = Capitalize(enum_value))) value_defs.append(tmpl_enum_value_def.substitute(mapping, value = enum_value, Value = Capitalize(enum_value))) type_decls.append(tmpl_enum.substitute(mapping, values = "".join(values))) type_impls.append(tmpl_enum.substitute(mapping, values = "".join(value_defs))) def ResolveRef(json, mapping): dot_pos = json["$ref"].find(".") if dot_pos == -1: domain_name = mapping["Domain"] type_name = json["$ref"] else: domain_name = json["$ref"][:dot_pos] type_name = json["$ref"][dot_pos + 1:] json_type = types["%s.%s" % (domain_name, type_name)] mapping["declared_name"] = Capitalize(type_name) mapping["Domain"] = domain_name mapping["domain"] = Uncamelcase(domain_name) mapping["param_type"] = tmpl_typename.substitute(mapping) ResolveType(json_type, mapping) if not "___type_declared" in json_type: json_type["___type_declared"] = True; if (json_type.get("type") == "object") and ("properties" in json_type): DeclareStruct(json_type["properties"], mapping) else: if ("enum" in json_type): DeclareEnum(json_type, mapping) type_decls.append(tmpl_typedef.substitute(mapping)) def ResolveArray(json, mapping): items_map = mapping.copy() ResolveType(json["items"], items_map) if items_map["Type"] == "List": # TODO(dgozman) Implement this. raise Exception("Nested arrays are not implemented") mapping["param_type"] = "std::vector<%s>" % items_map["param_type"] mapping["Type"] = "List" mapping["pass_type"] = "const %s&" % mapping["param_type"] mapping["storage_type"] = "std::vector<%s>" % items_map["storage_type"] mapping["raw_type"] = mapping["storage_type"] mapping["prep_req"] = tmpl_prep_req_list.substitute(mapping, item_type = items_map["storage_type"], item_init = items_map["init"], item_raw_type = items_map["raw_type"], item_pass = items_map["pass_template"].substitute(name="item", opt=""), ItemType = items_map["Type"]) mapping["arg_out"] = "&out_%s" % mapping["param"] def ResolveObject(json, mapping): mapping["Type"] = "Dictionary" mapping["storage_type"] = "scoped_ptr" mapping["raw_type"] = "base::DictionaryValue*" mapping["pass_template"] = tmpl_object_pass if "properties" in json: if not "declared_name" in mapping: mapping["declared_name"] = ("%s%s" % (mapping["Command"], Capitalize(mapping["proto_param"]))) mapping["param_type"] = ("scoped_refptr<%s>" % tmpl_typename.substitute(mapping)) DeclareStruct(json["properties"], mapping) else: mapping["param_type"] = ("scoped_refptr<%s>" % tmpl_typename.substitute(mapping)) mapping["pass_type"] = mapping["param_type"] mapping["arg_out"] = "&out_%s" % mapping["param"] else: mapping["param_type"] = "base::DictionaryValue" mapping["pass_type"] = "scoped_ptr" mapping["arg_out"] = "out_%s.get()" % mapping["param"] mapping["prep_req"] = tmpl_prep_req.substitute(mapping) def ResolvePrimitive(json, mapping): jsonrpc_type = json["type"] if jsonrpc_type == "boolean": mapping["param_type"] = "bool" mapping["Type"] = "Boolean" mapping["init"] = " = false" elif jsonrpc_type == "integer": mapping["param_type"] = "int" mapping["Type"] = "Integer" mapping["init"] = " = 0" elif jsonrpc_type == "number": mapping["param_type"] = "double" mapping["Type"] = "Double" mapping["init"] = " = 0.0" elif jsonrpc_type == "string": mapping["param_type"] = "std::string" mapping["pass_type"] = "const std::string&" mapping["Type"] = "String" if "enum" in json and not "declared_name" in mapping: if not "subdomain" in mapping: mapping["subdomain"] = Uncamelcase(mapping["command"]) DeclareEnum(json, mapping) else: raise Exception("Unknown type: %s" % json_type) mapping["storage_type"] = mapping["param_type"] mapping["raw_type"] = mapping["param_type"] mapping["prep_req"] = tmpl_prep_req.substitute(mapping) if jsonrpc_type != "string": mapping["pass_type"] = mapping["param_type"] mapping["arg_out"] = "&out_%s" % mapping["param"] def ResolveType(json, mapping): mapping["init"] = "" mapping["pass_template"] = string.Template("${opt}${name}") if "$ref" in json: ResolveRef(json, mapping) elif "type" in json: jsonrpc_type = json["type"] if jsonrpc_type == "array": ResolveArray(json, mapping) elif jsonrpc_type == "object": ResolveObject(json, mapping) else: ResolvePrimitive(json, mapping) else: raise Exception("Unknown type at %s.%s %s" % (mapping["Domain"], mapping["command"], mapping["proto_param"])) setters = [] fields = [] includes = [] fields_init = [] for json_domain in all_domains: domain_map = {} domain_map["Domain"] = json_domain["domain"] domain_map["domain"] = Uncamelcase(json_domain["domain"]) initializations = [] client_methods = [] client_method_impls = [] domain_empty = True domain_needs_client = False if "commands" in json_domain: for json_command in json_domain["commands"]: if (not ("handlers" in json_command) or not ("browser" in json_command["handlers"])): continue domain_empty = False command_map = domain_map.copy() command_map["command"] = json_command["name"] command_map["Command"] = Capitalize(json_command["name"]) if "redirect" in json_command: redirect_domain = json_command["redirect"] if not (redirect_domain in redirects): redirects[redirect_domain] = [] command_map["TargetDomain"] = redirect_domain redirects[redirect_domain].append(tmpl_register.substitute(command_map)) continue command_map["TargetDomain"] = command_map["Domain"] prep = [] args = [] if "parameters" in json_command: for json_param in json_command["parameters"]: param_map = command_map.copy() param_map["proto_param"] = json_param["name"] param_map["param"] = Uncamelcase(json_param["name"]) ResolveType(json_param, param_map) if json_param.get("optional"): if param_map["Type"] in ["List"]: # TODO(vkuzkokov) Implement transformation of base::ListValue # to std::vector and base::DictonaryValue to struct. raise Exception( "Optional array parameters are not implemented") prep.append(tmpl_prep_opt.substitute(param_map)) param_pass = param_map["pass_template"].substitute( name=tmpl_arg_name.substitute(param_map), opt="&") args.append( tmpl_arg_opt.substitute(param_map, param_pass=param_pass)) else: prep.append(param_map["prep_req"]) param_pass = param_map["pass_template"].substitute( name=tmpl_arg_name.substitute(param_map), opt="") args.append( tmpl_arg_req.substitute(param_map, param_pass=param_pass)) if json_command.get("async"): domain_needs_client = True json_returns = [] if "returns" in json_command: json_returns = json_command["returns"] command_map["declared_name"] = "%sResponse" % command_map["Command"] DeclareStruct(json_returns, command_map) # TODO(vkuzkokov) Pass async callback instance similar to how # InspectorBackendDispatcher does it. This, however, can work # only if Blink and Chrome are in the same repo. args.insert(0, "command_id") handler_method_impls.append( tmpl_callback_async_impl.substitute(command_map, prep = "".join(prep), args = "\n " + ",\n ".join(args))) client_methods.append(tmpl_response.substitute(command_map)) client_method_impls.append(tmpl_response_impl.substitute(command_map)) else: wrap = [] if "returns" in json_command: for json_param in json_command["returns"]: param_map = command_map.copy() param_map["proto_param"] = json_param["name"] param_map["param"] = Uncamelcase(json_param["name"]) if json_param.get("optional"): # TODO(vkuzkokov) Implement Optional for value types. raise Exception("Optional return values are not implemented") ResolveType(json_param, param_map) prep.append(tmpl_prep_output.substitute(param_map)) args.append(param_map["arg_out"]) wrap.append(tmpl_wrap.substitute(param_map)) args_str = "" if len(args) > 0: args_str = "\n " + ",\n ".join(args) handler_method_impls.append(tmpl_callback_impl.substitute(command_map, prep = "".join(prep), args = args_str, wrap = "".join(wrap))) initializations.append(tmpl_register.substitute(command_map)) handler_methods.append(tmpl_callback.substitute(command_map)) if "events" in json_domain: for json_event in json_domain["events"]: if (not ("handlers" in json_event) or not ("browser" in json_event["handlers"])): continue domain_empty = False domain_needs_client = True event_map = domain_map.copy() event_map["command"] = json_event["name"] event_map["Command"] = Capitalize(json_event["name"]) json_parameters = [] if "parameters" in json_event: json_parameters = json_event["parameters"] event_map["declared_name"] = "%sParams" % event_map["Command"] DeclareStruct(json_parameters, event_map); client_methods.append(tmpl_event.substitute(event_map)) client_method_impls.append(tmpl_event_impl.substitute(event_map)) if domain_empty: continue type_decls.append(tmpl_handler.substitute(domain_map)) setters.append(tmpl_setter.substitute(domain_map)) fields.append(tmpl_field.substitute(domain_map)) includes.append(tmpl_include.substitute(domain_map)) fields_init.append(tmpl_field_init.substitute(domain_map)) if domain_needs_client: type_decls.append(tmpl_client.substitute(domain_map, methods = "".join(client_methods))) initializations.append(tmpl_init_client.substitute(domain_map)) type_impls.append(tmpl_client_impl.substitute(domain_map, methods = "\n".join(client_method_impls))) domain_map["initializations"] = "".join(initializations) domain_maps.append(domain_map) for domain_map in domain_maps: domain = domain_map["Domain"] if domain in redirects: domain_map["initializations"] += "".join(redirects[domain]) handler_method_impls.append(tmpl_setter_impl.substitute(domain_map)) output_h_file = open(output_h_path, "w") output_cc_file = open(output_cc_path, "w") output_h_file.write(template_h.substitute({}, types = "\n".join(type_decls), setters = "".join(setters), methods = "".join(handler_methods), fields = "".join(fields))) output_h_file.close() output_cc_file.write(template_cc.substitute({}, major = blink_protocol["version"]["major"], minor = blink_protocol["version"]["minor"], includes = "".join(sorted(includes)), fields_init = ",\n ".join(fields_init), methods = "\n".join(handler_method_impls), types = "\n".join(type_impls))) output_cc_file.close()