1 # Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 package CodeGeneratorInspector;
13 $typeTransform{"InspectorClient"} = {
14 "forward" => "InspectorClient",
15 "header" => "InspectorClient.h",
17 $typeTransform{"Backend"} = {
18 "forward" => "InspectorBackend",
19 "header" => "InspectorBackend.h",
20 "domainAccessor" => "m_inspectorController->inspectorBackend()",
22 $typeTransform{"Controller"} = {
23 "forwardHeader" => "InspectorController.h",
24 "domainAccessor" => "m_inspectorController",
26 $typeTransform{"Debug"} = {
27 "forward" => "InspectorDebuggerAgent",
28 "header" => "InspectorDebuggerAgent.h",
29 "domainAccessor" => "m_inspectorController->debuggerAgent()",
31 $typeTransform{"Resource"} = {
32 "forward" => "InspectorResourceAgent",
33 "header" => "InspectorResourceAgent.h",
34 "domainAccessor" => "m_inspectorController->m_resourceAgent",
36 $typeTransform{"DOM"} = {
37 "forward" => "InspectorDOMAgent",
38 "header" => "InspectorDOMAgent.h",
39 "domainAccessor" => "m_inspectorController->domAgent()",
41 $typeTransform{"CSS"} = {
42 "forward" => "InspectorCSSAgent",
43 "header" => "InspectorCSSAgent.h",
44 "domainAccessor" => "m_inspectorController->cssAgent()",
46 $typeTransform{"ApplicationCache"} = {
47 "forward" => "InspectorApplicationCacheAgent",
48 "header" => "InspectorApplicationCacheAgent.h",
49 "domainAccessor" => "m_inspectorController->applicationCacheAgent()",
51 $typeTransform{"FileSystem"} = {
52 "forward" => "InspectorFileSystemAgent",
53 "header" => "InspectorFileSystemAgent.h",
54 "domainAccessor" => "m_inspectorController->fileSystemAgent()",
56 $typeTransform{"Profiler"} = {
57 "forward" => "InspectorProfilerAgent",
58 "header" => "InspectorProfilerAgent.h",
59 "domainAccessor" => "m_inspectorController->profilerAgent()",
61 $typeTransform{"Frontend"} = {
62 "forward" => "InspectorFrontend",
63 "header" => "InspectorFrontend.h",
65 $typeTransform{"PassRefPtr"} = {
66 "forwardHeader" => "wtf/PassRefPtr.h",
68 $typeTransform{"Object"} = {
69 "param" => "PassRefPtr<InspectorObject>",
70 "variable" => "RefPtr<InspectorObject>",
71 "defaultValue" => "InspectorObject::create()",
72 "forward" => "InspectorObject",
73 "header" => "InspectorValues.h",
74 "JSONType" => "Object"
76 $typeTransform{"Array"} = {
77 "param" => "PassRefPtr<InspectorArray>",
78 "variable" => "RefPtr<InspectorArray>",
79 "defaultValue" => "InspectorArray::create()",
80 "forward" => "InspectorArray",
81 "header" => "InspectorValues.h",
84 $typeTransform{"Value"} = {
85 "param" => "PassRefPtr<InspectorValue>",
86 "variable" => "RefPtr<InspectorValue>",
87 "defaultValue" => "InspectorValue::null()",
88 "forward" => "InspectorValue",
89 "header" => "InspectorValues.h",
92 $typeTransform{"String"} = {
93 "param" => "const String&",
94 "variable" => "String",
95 "defaultValue" => "\"\"",
96 "forwardHeader" => "wtf/Forward.h",
97 "header" => "PlatformString.h",
98 "JSONType" => "String"
100 $typeTransform{"long"} = {
102 "variable" => "long",
103 "defaultValue" => "0",
106 "JSONType" => "Number"
108 $typeTransform{"int"} = {
111 "defaultValue" => "0",
114 "JSONType" => "Number",
116 $typeTransform{"unsigned long"} = {
117 "param" => "unsigned long",
118 "variable" => "unsigned long",
119 "defaultValue" => "0u",
122 "JSONType" => "Number"
124 $typeTransform{"unsigned int"} = {
125 "param" => "unsigned int",
126 "variable" => "unsigned int",
127 "defaultValue" => "0u",
130 "JSONType" => "Number"
132 $typeTransform{"double"} = {
134 "variable" => "double",
135 "defaultValue" => "0.0",
138 "JSONType" => "Number"
140 $typeTransform{"boolean"} = {
143 "defaultValue" => "false",
146 "JSONType" => "Boolean"
148 $typeTransform{"void"} = {
153 # Default License Templates
155 my $licenseTemplate = << "EOF";
156 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
157 // Use of this source code is governed by a BSD-style license that can be
158 // found in the LICENSE file.
163 my $outputHeadersDir;
164 my $writeDependencies;
169 my $backendClassName;
170 my $backendJSStubName;
173 my @backendMethodsImpl;
174 my $backendConstructor;
175 my @backendConstantDeclarations;
176 my @backendConstantDefinitions;
180 my $frontendClassName;
183 my @frontendMethodsImpl;
184 my $frontendConstructor;
185 my @frontendConstantDeclarations;
186 my @frontendConstantDefinitions;
189 # Default constructor
195 $codeGenerator = shift;
197 $outputHeadersDir = shift;
198 shift; # $useLayerOnTop
199 shift; # $preprocessor
200 $writeDependencies = shift;
203 bless($reference, $object);
207 # Params: 'idlDocument' struct
211 my $dataNode = shift;
213 $namespace = $dataNode->module;
214 $namespace =~ s/core/WebCore/;
217 # Params: 'idlDocument' struct
218 sub GenerateInterface
221 my $interface = shift;
224 my $className = $interface->name;
226 $frontendClassName = $className . "Frontend";
227 $frontendConstructor = " ${frontendClassName}(InspectorClient* inspectorClient) : m_inspectorClient(inspectorClient) { }";
228 $frontendFooter = " InspectorClient* m_inspectorClient;";
229 $frontendTypes{"String"} = 1;
230 $frontendTypes{"InspectorClient"} = 1;
231 $frontendTypes{"PassRefPtr"} = 1;
233 $backendClassName = $className . "BackendDispatcher";
234 $backendJSStubName = $className . "BackendStub";
236 push(@backendHead, " ${backendClassName}(InspectorController* inspectorController) : m_inspectorController(inspectorController) { }");
237 push(@backendHead, " void reportProtocolError(const long callId, const String& errorText) const;");
238 push(@backendHead, " void dispatch(const String& message);");
239 push(@backendHead, " static bool getCommandName(const String& message, String* result);");
240 $backendConstructor = join("\n", @backendHead);
241 $backendFooter = " InspectorController* m_inspectorController;";
242 $backendTypes{"Controller"} = 1;
243 $backendTypes{"InspectorClient"} = 1;
244 $backendTypes{"PassRefPtr"} = 1;
245 $backendTypes{"Object"} = 1;
247 push(@backendMethodsImpl, generateBackendMessageParser());
248 generateFunctions($interface);
250 # Make dispatcher methods private on the backend.
251 push(@backendConstantDeclarations, "");
252 push(@backendConstantDeclarations, "private:");
255 sub generateFunctions
257 my $interface = shift;
259 foreach my $function (@{$interface->functions}) {
260 if ($function->signature->extendedAttributes->{"notify"}) {
261 generateFrontendFunction($function);
263 generateBackendFunction($function);
266 push(@backendMethodsImpl, generateBackendDispatcher());
267 push(@backendMethodsImpl, generateBackendReportProtocolError());
269 @backendStubJS = generateBackendStubJS($interface);
272 sub generateFrontendFunction
274 my $function = shift;
276 my $functionName = $function->signature->name;
278 my @argsFiltered = grep($_->direction eq "out", @{$function->parameters}); # just keep only out parameters for frontend interface.
279 map($frontendTypes{$_->type} = 1, @argsFiltered); # register required types.
280 my $arguments = join(", ", map($typeTransform{$_->type}->{"param"} . " " . $_->name, @argsFiltered)); # prepare arguments for function signature.
282 my $signature = " void ${functionName}(${arguments});";
283 if (!$frontendMethods{${signature}}) {
284 $frontendMethods{${signature}} = 1;
287 push(@function, "void ${frontendClassName}::${functionName}(${arguments})");
288 push(@function, "{");
289 push(@function, " RefPtr<InspectorObject> ${functionName}Message = InspectorObject::create();");
290 push(@function, " ${functionName}Message->setString(\"type\", \"event\");");
291 push(@function, " ${functionName}Message->setString(\"event\", \"$functionName\");");
292 push(@function, " RefPtr<InspectorObject> payloadDataObject = InspectorObject::create();");
293 my @pushArguments = map(" payloadDataObject->set" . $typeTransform{$_->type}->{"JSONType"} . "(\"" . $_->name . "\", " . $_->name . ");", @argsFiltered);
294 push(@function, @pushArguments);
295 push(@function, " ${functionName}Message->setObject(\"data\", payloadDataObject);");
296 push(@function, " m_inspectorClient->sendMessageToFrontend(${functionName}Message->toJSONString());");
298 push(@function, "}");
300 push(@frontendMethodsImpl, @function);
304 sub generateBackendFunction
306 my $function = shift;
308 my $functionName = $function->signature->name;
310 push(@backendConstantDeclarations, " static const char* ${functionName}Cmd;");
311 push(@backendConstantDefinitions, "const char* ${backendClassName}::${functionName}Cmd = \"${functionName}\";");
313 map($backendTypes{$_->type} = 1, @{$function->parameters}); # register required types
314 my @inArgs = grep($_->direction eq "in" && !($_->name eq "callId") , @{$function->parameters});
315 my @outArgs = grep($_->direction eq "out", @{$function->parameters});
317 my $signature = " void ${functionName}(long callId, InspectorObject* requestMessageObject);";
318 !$backendMethods{${signature}} || die "Duplicate function was detected for signature '$signature'.";
319 $backendMethods{${signature}} = $functionName;
322 my $requestMessageObject = scalar(@inArgs) ? " requestMessageObject" : "";
323 push(@function, "void ${backendClassName}::${functionName}(long callId, InspectorObject*$requestMessageObject)");
324 push(@function, "{");
325 push(@function, " RefPtr<InspectorArray> protocolErrors = InspectorArray::create();");
328 my $domain = $function->signature->extendedAttributes->{"handler"} || "Controller";
329 my $domainAccessor = $typeTransform{$domain}->{"domainAccessor"};
330 $backendTypes{$domain} = 1;
331 push(@function, " if (!$domainAccessor)");
332 push(@function, " protocolErrors->pushString(\"Protocol Error: $domain handler is not available.\");");
335 if (scalar(@inArgs)) {
336 # declare variables for all 'in' args;
337 push(@function, map(" " . $typeTransform{$_->type}->{"variable"} . " " . $_->name . " = " . $typeTransform{$_->type}->{"defaultValue"} . ";", @inArgs));
340 push(@function, " RefPtr<InspectorObject> argumentsContainer;");
341 push(@function, " if (!(argumentsContainer = requestMessageObject->getObject(\"arguments\"))) {");
342 push(@function, " protocolErrors->pushString(\"Protocol Error: 'arguments' property with type 'object' was not found.\");");
343 push(@function, " } else {");
344 push(@function, " InspectorObject::const_iterator argumentsEndIterator = argumentsContainer->end();");
346 foreach my $parameter (@inArgs) {
347 my $name = $parameter->name;
348 my $type = $parameter->type;
349 my $variableType = $typeTransform{$type}->{"variable"};
350 my $JSONType = $typeTransform{$type}->{"JSONType"};
353 push(@function, " InspectorObject::const_iterator ${name}ValueIterator = argumentsContainer->find(\"$name\");");
354 push(@function, " if (${name}ValueIterator == argumentsEndIterator) {");
355 push(@function, " protocolErrors->pushString(\"Protocol Error: Argument '$name' with type '$JSONType' was not found.\");");
356 push(@function, " } else {");
357 push(@function, " if (!${name}ValueIterator->second->as$JSONType(&$name)) {");
358 push(@function, " protocolErrors->pushString(\"Protocol Error: Argument '$name' has wrong type. It should be '$JSONType'.\");");
359 push(@function, " }");
360 push(@function, " }");
362 push(@function, " }");
365 # declare local variables for out arguments.
366 push(@function, map(" " . $typeTransform{$_->type}->{"variable"} . " " . $_->name . " = " . $typeTransform{$_->type}->{"defaultValue"} . ";", @outArgs));
368 my $args = join(", ", (map($_->name, @inArgs), map("&" . $_->name, @outArgs)));
369 push(@function, " if (!protocolErrors->length())");
370 push(@function, " $domainAccessor->$functionName($args);");
373 push(@function, " // use InspectorFrontend as a marker of WebInspector availability");
374 push(@function, " if ((callId || protocolErrors->length()) && m_inspectorController->hasFrontend()) {");
375 push(@function, " RefPtr<InspectorObject> responseMessage = InspectorObject::create();");
376 push(@function, " responseMessage->setNumber(\"seq\", callId);");
377 push(@function, " responseMessage->setBoolean(\"success\", !protocolErrors->length());");
379 push(@function, " if (protocolErrors->length())");
380 push(@function, " responseMessage->setArray(\"errors\", protocolErrors);");
381 if (scalar(@outArgs)) {
382 push(@function, " else {");
383 push(@function, " RefPtr<InspectorObject> responseData = InspectorObject::create();");
384 push(@function, map(" responseData->set" . $typeTransform{$_->type}->{"JSONType"} . "(\"" . $_->name . "\", " . $_->name . ");", @outArgs));
385 push(@function, " responseMessage->setObject(\"data\", responseData);");
386 push(@function, " }");
388 push(@function, " m_inspectorController->inspectorClient()->sendMessageToFrontend(responseMessage->toJSONString());");
389 push(@function, " }");
392 push(@function, "}");
394 push(@backendMethodsImpl, @function);
397 sub generateBackendReportProtocolError
399 my $reportProtocolError = << "EOF";
401 void ${backendClassName}::reportProtocolError(const long callId, const String& errorText) const
403 RefPtr<InspectorObject> message = InspectorObject::create();
404 message->setNumber("seq", callId);
405 message->setBoolean("success", false);
406 RefPtr<InspectorArray> errors = InspectorArray::create();
407 errors->pushString(errorText);
408 message->setArray("errors", errors);
409 m_inspectorController->inspectorClient()->sendMessageToFrontend(message->toJSONString());
412 return split("\n", $reportProtocolError);
416 sub generateBackendDispatcher
419 my @methods = map($backendMethods{$_}, keys %backendMethods);
420 my @mapEntries = map(" dispatchMap.add(${_}Cmd, &${backendClassName}::$_);", @methods);
421 my $mapEntries = join("\n", @mapEntries);
423 my $backendDispatcherBody = << "EOF";
424 void ${backendClassName}::dispatch(const String& message)
426 typedef void (${backendClassName}::*CallHandler)(long callId, InspectorObject* messageObject);
427 typedef HashMap<String, CallHandler> DispatchMap;
428 DEFINE_STATIC_LOCAL(DispatchMap, dispatchMap, );
431 if (dispatchMap.isEmpty()) {
435 RefPtr<InspectorValue> parsedMessage = InspectorValue::parseJSON(message);
436 if (!parsedMessage) {
437 reportProtocolError(callId, "Protocol Error: Invalid message format. Message should be in JSON format.");
441 RefPtr<InspectorObject> messageObject = parsedMessage->asObject();
442 if (!messageObject) {
443 reportProtocolError(callId, "Protocol Error: Invalid message format. The message should be a JSONified object.");
447 RefPtr<InspectorValue> commandValue = messageObject->get("command");
449 reportProtocolError(callId, "Protocol Error: Invalid message format. 'command' property wasn't found.");
454 if (!commandValue->asString(&command)) {
455 reportProtocolError(callId, "Protocol Error: Invalid message format. The type of 'command' property should be string.");
459 RefPtr<InspectorValue> callIdValue = messageObject->get("seq");
461 reportProtocolError(callId, "Protocol Error: Invalid message format. 'seq' property was not found in the request.");
465 if (!callIdValue->asNumber(&callId)) {
466 reportProtocolError(callId, "Protocol Error: Invalid message format. The type of 'seq' property should be number.");
470 HashMap<String, CallHandler>::iterator it = dispatchMap.find(command);
471 if (it == dispatchMap.end()) {
472 reportProtocolError(callId, makeString("Protocol Error: Invalid command was received. '", command, "' wasn't found."));
476 ((*this).*it->second)(callId, messageObject.get());
479 return split("\n", $backendDispatcherBody);
482 sub generateBackendMessageParser
484 my $messageParserBody = << "EOF";
485 bool ${backendClassName}::getCommandName(const String& message, String* result)
487 RefPtr<InspectorValue> value = InspectorValue::parseJSON(message);
491 RefPtr<InspectorObject> object = value->asObject();
495 RefPtr<InspectorValue> commandValue = object->get("command");
499 return commandValue->asString(result);
503 return split("\n", $messageParserBody);
506 sub generateBackendStubJS
508 my $interface = shift;
509 my @backendFunctions = grep(!$_->signature->extendedAttributes->{"notify"}, @{$interface->functions});
512 foreach my $function (@backendFunctions) {
513 my $name = $function->signature->name;
514 my $domain = $function->signature->extendedAttributes->{"handler"};
515 my $argumentNames = join(",", map("\"" . $_->name . "\": \"" . lc($typeTransform{$_->type}->{"JSONType"}) . "\"", grep($_->direction eq "in", @{$function->parameters})));
516 push(@JSStubs, " this._registerDelegate('{" .
518 "\"domain\": \"$domain\", " .
519 "\"command\": \"$name\", " .
520 "\"arguments\": {$argumentNames}" .
524 my $JSStubs = join("\n", @JSStubs);
525 my $inspectorBackendStubJS = << "EOF";
528 WebInspector.InspectorBackendStub = function()
533 WebInspector.InspectorBackendStub.prototype = {
534 _registerDelegate: function(commandInfo)
536 var commandObject = JSON.parse(commandInfo);
537 this[commandObject.command] = this.sendMessageToBackend.bind(this, commandInfo);
540 sendMessageToBackend: function()
542 var args = Array.prototype.slice.call(arguments);
543 var request = JSON.parse(args.shift());
545 for (var key in request.arguments) {
546 if (args.length === 0) {
547 console.error("Protocol Error: Invalid number of arguments for 'InspectorBackend.%s' call. It should have the next arguments '%s'.", request.command, JSON.stringify(request.arguments));
550 var value = args.shift();
551 if (typeof value !== request.arguments[key]) {
552 console.error("Protocol Error: Invalid type of argument '%s' for 'InspectorBackend.%s' call. It should be '%s' but it is '%s'.", key, request.command, request.arguments[key], typeof value);
555 request.arguments[key] = value;
558 if (args.length === 1) {
559 if (typeof args[0] !== "function" && typeof args[0] !== "undefined") {
560 console.error("Protocol Error: Optional callback argument for 'InspectorBackend.%s' call should be a function but its type is '%s'.", request.command, typeof args[0]);
563 request.seq = WebInspector.Callback.wrap(args[0]);
566 var message = JSON.stringify(request);
567 InspectorFrontendHost.sendMessageToBackend(message);
571 InspectorBackend = new WebInspector.InspectorBackendStub();
574 return split("\n", $inspectorBackendStubJS);
579 my $className = shift;
581 my $constructor = shift;
582 my $constants = shift;
586 my $forwardHeaders = join("\n", sort(map("#include <" . $typeTransform{$_}->{"forwardHeader"} . ">", grep($typeTransform{$_}->{"forwardHeader"}, keys %{$types}))));
587 my $forwardDeclarations = join("\n", sort(map("class " . $typeTransform{$_}->{"forward"} . ";", grep($typeTransform{$_}->{"forward"}, keys %{$types}))));
588 my $constantDeclarations = join("\n", @{$constants});
589 my $methodsDeclarations = join("\n", keys %{$methods});
591 my $headerBody = << "EOF";
592 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
593 // Use of this source code is governed by a BSD-style license that can be
594 // found in the LICENSE file.
595 #ifndef ${className}_h
596 #define ${className}_h
600 namespace $namespace {
608 $constantDeclarations
615 } // namespace $namespace
616 #endif // !defined(${className}_h)
624 my $className = shift;
626 my $constants = shift;
629 my @sourceContent = split("\r", $licenseTemplate);
630 push(@sourceContent, "\n#include \"config.h\"");
631 push(@sourceContent, "#include \"$className.h\"");
632 push(@sourceContent, "#include <wtf/text/StringConcatenate.h>");
633 push(@sourceContent, "");
634 push(@sourceContent, "#if ENABLE(INSPECTOR)");
635 push(@sourceContent, "");
638 foreach my $type (keys %{$types}) {
639 $headers{"#include \"" . $typeTransform{$type}->{"header"} . "\""} = 1 if !$typeTransform{$type}->{"header"} eq "";
641 push(@sourceContent, sort keys %headers);
642 push(@sourceContent, "");
643 push(@sourceContent, "namespace $namespace {");
644 push(@sourceContent, "");
645 push (@sourceContent, join("\n", @{$constants}));
646 push(@sourceContent, "");
647 push(@sourceContent, @{$methods});
648 push(@sourceContent, "");
649 push(@sourceContent, "} // namespace $namespace");
650 push(@sourceContent, "");
651 push(@sourceContent, "#endif // ENABLE(INSPECTOR)");
652 push(@sourceContent, "");
653 return @sourceContent;
660 open(my $SOURCE, ">$outputDir/$frontendClassName.cpp") || die "Couldn't open file $outputDir/$frontendClassName.cpp";
661 print $SOURCE join("\n", generateSource($frontendClassName, \%frontendTypes, \@frontendConstantDefinitions, \@frontendMethodsImpl));
665 open(my $HEADER, ">$outputHeadersDir/$frontendClassName.h") || die "Couldn't open file $outputHeadersDir/$frontendClassName.h";
666 print $HEADER generateHeader($frontendClassName, \%frontendTypes, $frontendConstructor, \@frontendConstantDeclarations, \%frontendMethods, $frontendFooter);
670 open($SOURCE, ">$outputDir/$backendClassName.cpp") || die "Couldn't open file $outputDir/$backendClassName.cpp";
671 print $SOURCE join("\n", generateSource($backendClassName, \%backendTypes, \@backendConstantDefinitions, \@backendMethodsImpl));
675 open($HEADER, ">$outputHeadersDir/$backendClassName.h") || die "Couldn't open file $outputHeadersDir/$backendClassName.h";
676 print $HEADER join("\n", generateHeader($backendClassName, \%backendTypes, $backendConstructor, \@backendConstantDeclarations, \%backendMethods, $backendFooter));
680 open(my $JS_STUB, ">$outputDir/$backendJSStubName.js") || die "Couldn't open file $outputDir/$backendJSStubName.js";
681 print $JS_STUB join("\n", @backendStubJS);