2a407b9b55c1f686b217a60ed45e639552e02eba
[WebKit-https.git] / Source / WebCore / inspector / CodeGeneratorInspector.pm
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.
4
5 package CodeGeneratorInspector;
6
7 use strict;
8
9 use Class::Struct;
10 use File::stat;
11
12 my %typeTransform;
13 $typeTransform{"ApplicationCache"} = {
14     "forward" => "InspectorApplicationCacheAgent",
15     "header" => "InspectorApplicationCacheAgent.h",
16     "domainAccessor" => "m_inspectorAgent->applicationCacheAgent()",
17 };
18 $typeTransform{"CSS"} = {
19     "forward" => "InspectorCSSAgent",
20     "header" => "InspectorCSSAgent.h",
21     "domainAccessor" => "m_inspectorAgent->cssAgent()",
22 };
23 $typeTransform{"Console"} = {
24     "forward" => "InspectorConsoleAgent",
25     "header" => "InspectorConsoleAgent.h",
26     "domainAccessor" => "m_inspectorAgent->consoleAgent()",
27 };
28 $typeTransform{"Debugger"} = {
29     "forward" => "InspectorDebuggerAgent",
30     "header" => "InspectorDebuggerAgent.h",
31     "domainAccessor" => "m_inspectorAgent->debuggerAgent()",
32 };
33 $typeTransform{"BrowserDebugger"} = {
34     "forward" => "InspectorBrowserDebuggerAgent",
35     "header" => "InspectorBrowserDebuggerAgent.h",
36     "domainAccessor" => "m_inspectorAgent->browserDebuggerAgent()",
37 };
38 $typeTransform{"Database"} = {
39     "forward" => "InspectorDatabaseAgent",
40     "header" => "InspectorDatabaseAgent.h",
41     "domainAccessor" => "m_inspectorAgent->databaseAgent()",
42 };
43 $typeTransform{"DOM"} = {
44     "forward" => "InspectorDOMAgent",
45     "header" => "InspectorDOMAgent.h",
46     "domainAccessor" => "m_inspectorAgent->domAgent()",
47 };
48 $typeTransform{"DOMStorage"} = {
49     "forward" => "InspectorDOMStorageAgent",
50     "header" => "InspectorDOMStorageAgent.h",
51     "domainAccessor" => "m_inspectorAgent->domStorageAgent()",
52 };
53 $typeTransform{"FileSystem"} = {
54     "forward" => "InspectorFileSystemAgent",
55     "header" => "InspectorFileSystemAgent.h",
56     "domainAccessor" => "m_inspectorAgent->fileSystemAgent()",
57 };
58 $typeTransform{"Inspector"} = {
59     "forwardHeader" => "InspectorAgent.h",
60     "domainAccessor" => "m_inspectorAgent",
61 };
62 $typeTransform{"Network"} = {
63     "forward" => "InspectorResourceAgent",
64     "header" => "InspectorResourceAgent.h",
65     "domainAccessor" => "m_inspectorAgent->resourceAgent()",
66 };
67 $typeTransform{"Profiler"} = {
68     "forward" => "InspectorProfilerAgent",
69     "header" => "InspectorProfilerAgent.h",
70     "domainAccessor" => "m_inspectorAgent->profilerAgent()",
71 };
72 $typeTransform{"Runtime"} = {
73     "forward" => "InspectorRuntimeAgent",
74     "header" => "InspectorRuntimeAgent.h",
75     "domainAccessor" => "m_inspectorAgent->runtimeAgent()",
76 };
77 $typeTransform{"Timeline"} = {
78     "forward" => "InspectorTimelineAgent",
79     "header" => "InspectorTimelineAgent.h",
80     "domainAccessor" => "m_inspectorAgent->timelineAgent()",
81 };
82
83 $typeTransform{"Frontend"} = {
84     "forward" => "InspectorFrontend",
85     "header" => "InspectorFrontend.h",
86 };
87 $typeTransform{"InspectorClient"} = {
88     "forward" => "InspectorClient",
89     "header" => "InspectorClient.h",
90 };
91 $typeTransform{"PassRefPtr"} = {
92     "forwardHeader" => "wtf/PassRefPtr.h",
93 };
94 $typeTransform{"Object"} = {
95     "param" => "PassRefPtr<InspectorObject>",
96     "variable" => "RefPtr<InspectorObject>",
97     "defaultValue" => "InspectorObject::create()",
98     "forward" => "InspectorObject",
99     "header" => "InspectorValues.h",
100     "JSONType" => "Object",
101     "JSType" => "object",
102     "DocType" => "%s"
103 };
104 $typeTransform{"Array"} = {
105     "param" => "PassRefPtr<InspectorArray>",
106     "variable" => "RefPtr<InspectorArray>",
107     "defaultValue" => "InspectorArray::create()",
108     "forward" => "InspectorArray",
109     "header" => "InspectorValues.h",
110     "JSONType" => "Array",
111     "JSType" => "object",
112     "DocType" => "array of %s"
113 };
114 $typeTransform{"Value"} = {
115     "param" => "PassRefPtr<InspectorValue>",
116     "variable" => "RefPtr<InspectorValue>",
117     "defaultValue" => "InspectorValue::null()",
118     "forward" => "InspectorValue",
119     "header" => "InspectorValues.h",
120     "JSONType" => "Value",
121     "JSType" => "",
122     "DocType" => "value"
123 };
124 $typeTransform{"String"} = {
125     "param" => "const String&",
126     "variable" => "String",
127     "return" => "String",
128     "defaultValue" => "\"\"",
129     "forwardHeader" => "PlatformString.h",
130     "header" => "PlatformString.h",
131     "JSONType" => "String",
132     "JSType" => "string"
133 };
134 $typeTransform{"long"} = {
135     "param" => "long",
136     "variable" => "long",
137     "defaultValue" => "0",
138     "forward" => "",
139     "header" => "",
140     "JSONType" => "Number",
141     "JSType" => "number"
142 };
143 $typeTransform{"int"} = {
144     "param" => "int",
145     "variable" => "int",
146     "defaultValue" => "0",
147     "forward" => "",
148     "header" => "",
149     "JSONType" => "Number",
150     "JSType" => "number"
151 };
152 $typeTransform{"unsigned long"} = {
153     "param" => "unsigned long",
154     "variable" => "unsigned long",
155     "defaultValue" => "0u",
156     "forward" => "",
157     "header" => "",
158     "JSONType" => "Number",
159     "JSType" => "number"
160 };
161 $typeTransform{"unsigned int"} = {
162     "param" => "unsigned int",
163     "variable" => "unsigned int",
164     "defaultValue" => "0u",
165     "forward" => "",
166     "header" => "",
167     "JSONType" => "Number",
168     "JSType" => "number"
169 };
170 $typeTransform{"double"} = {
171     "param" => "double",
172     "variable" => "double",
173     "defaultValue" => "0.0",
174     "forward" => "",
175     "header" => "",
176     "JSONType" => "Number",
177     "JSType" => "number"
178 };
179 $typeTransform{"boolean"} = {
180     "param" => "bool",
181     "variable"=> "bool",
182     "defaultValue" => "false",
183     "forward" => "",
184     "header" => "",
185     "JSONType" => "Boolean",
186     "JSType" => "boolean"
187 };
188 $typeTransform{"void"} = {
189     "forward" => "",
190     "header" => ""
191 };
192
193 # Default License Templates
194
195 my $licenseTemplate = << "EOF";
196 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
197 // Use of this source code is governed by a BSD-style license that can be
198 // found in the LICENSE file.
199 EOF
200
201 my $codeGenerator;
202 my $outputDir;
203 my $outputHeadersDir;
204 my $writeDependencies;
205 my $verbose;
206
207 my $namespace;
208
209 my $backendClassName;
210 my $backendJSStubName;
211 my %backendTypes;
212 my @backendMethods;
213 my @backendMethodsImpl;
214 my %backendMethodSignatures;
215 my $backendConstructor;
216 my @backendConstantDeclarations;
217 my @backendConstantDefinitions;
218 my $backendFooter;
219 my @backendJSStubs;
220
221 my $frontendClassName;
222 my %frontendTypes;
223 my @frontendMethods;
224 my @frontendAgentFields;
225 my @frontendMethodsImpl;
226 my %frontendMethodSignatures;
227 my $frontendConstructor;
228 my @frontendConstantDeclarations;
229 my @frontendConstantDefinitions;
230 my @frontendFooter;
231
232 my @documentationToc;
233 my @documentationLines;
234
235 # Default constructor
236 sub new
237 {
238     my $object = shift;
239     my $reference = { };
240
241     $codeGenerator = shift;
242     $outputDir = shift;
243     $outputHeadersDir = shift;
244     shift; # $useLayerOnTop
245     shift; # $preprocessor
246     $writeDependencies = shift;
247     $verbose = shift;
248
249     bless($reference, $object);
250     return $reference;
251 }
252
253 # Params: 'idlDocument' struct
254 sub GenerateModule
255 {
256     my $object = shift;
257     my $dataNode = shift;
258
259     $namespace = $dataNode->module;
260     $namespace =~ s/core/WebCore/;
261
262     $frontendClassName = "InspectorFrontend";
263     $frontendConstructor = "    ${frontendClassName}(InspectorClient*);";
264     push(@frontendFooter, "private:");
265     push(@frontendFooter, "    InspectorClient* m_inspectorClient;");
266     $frontendTypes{"String"} = 1;
267     $frontendTypes{"InspectorClient"} = 1;
268     $frontendTypes{"PassRefPtr"} = 1;
269
270     $backendClassName = "InspectorBackendDispatcher";
271     $backendJSStubName = "InspectorBackendStub";
272     my @backendHead;
273     push(@backendHead, "    ${backendClassName}(InspectorAgent* inspectorAgent) : m_inspectorAgent(inspectorAgent) { }");
274     push(@backendHead, "    void reportProtocolError(const long callId, const String& errorText) const;");
275     push(@backendHead, "    void dispatch(const String& message);");
276     push(@backendHead, "    static bool getCommandName(const String& message, String* result);");
277     $backendConstructor = join("\n", @backendHead);
278     $backendFooter = "    InspectorAgent* m_inspectorAgent;";
279     $backendTypes{"Inspector"} = 1;
280     $backendTypes{"InspectorClient"} = 1;
281     $backendTypes{"PassRefPtr"} = 1;
282     $backendTypes{"Object"} = 1;
283 }
284
285 # Params: 'idlDocument' struct
286 sub GenerateInterface
287 {
288     my $object = shift;
289     my $interface = shift;
290     my $defines = shift;
291
292     my %agent = (
293         methodDeclarations => [],
294         methodSignatures => {}
295     );
296     generateFunctions($interface, \%agent);
297     if (@{%agent->{methodDeclarations}}) {
298         generateAgentDeclaration($interface, \%agent);
299     }
300 }
301
302 sub generateAgentDeclaration
303 {
304     my $interface = shift;
305     my $agent = shift;
306     my $agentName = $interface->name;
307     push(@frontendMethods, "    class ${agentName} {");
308     push(@frontendMethods, "    public:");
309     push(@frontendMethods, "        ${agentName}(InspectorClient* inspectorClient) : m_inspectorClient(inspectorClient) { }");
310     push(@frontendMethods, @{$agent->{methodDeclarations}});
311     push(@frontendMethods, "    private:");
312     push(@frontendMethods, "        InspectorClient* m_inspectorClient;");
313     push(@frontendMethods, "    };");
314     push(@frontendMethods, "");
315
316     my $getterName = lc($agentName);
317     push(@frontendMethods, "    ${agentName}* ${getterName}() { return &m_${getterName}; }");
318     push(@frontendMethods, "");
319
320     push(@frontendFooter, "    ${agentName} m_${getterName};");
321
322     push(@frontendAgentFields, "m_${getterName}");
323 }
324
325 sub generateFrontendConstructorImpl
326 {
327     my @frontendConstructorImpl;
328     push(@frontendConstructorImpl, "${frontendClassName}::${frontendClassName}(InspectorClient* inspectorClient)");
329     push(@frontendConstructorImpl, "    : m_inspectorClient(inspectorClient)");
330     foreach my $agentField (@frontendAgentFields) {
331         push(@frontendConstructorImpl, "    , ${agentField}(inspectorClient)");
332     }
333     push(@frontendConstructorImpl, "{");
334     push(@frontendConstructorImpl, "}");
335     return @frontendConstructorImpl;
336 }
337
338 sub generateFunctions
339 {
340     my $interface = shift;
341     my $agent = shift;
342
343     foreach my $function (@{$interface->functions}) {
344         if ($function->signature->extendedAttributes->{"event"}) {
345             generateFrontendFunction($interface, $function, $agent);
346         } else {
347             generateBackendFunction($interface, $function);
348         }
349     }
350
351     push(@documentationToc, "<li><a href='#" . $interface->name . "'>" . $interface->name . "</a></li>");
352     push(@documentationLines, "<h2 id='" . $interface->name . "'><a name=" . $interface->name . "></a>" . $interface->name . "</h2>");
353
354     push(@documentationLines, "<h3>Events</h3>");
355     foreach my $function (grep($_->signature->extendedAttributes->{"event"}, @{$interface->functions}) ) {
356         generateDocumentationEvent($interface, $function);
357     }
358
359     push(@documentationLines, "<h3>Commands</h3>");
360     foreach my $function (grep(!$_->signature->extendedAttributes->{"event"}, @{$interface->functions})) {
361         generateDocumentationCommand($interface, $function);
362     }
363
364     collectBackendJSStubFunctions($interface);
365 }
366
367 sub generateFrontendFunction
368 {
369     my $interface = shift;
370     my $function = shift;
371     my $agent = shift;
372
373     my $functionName = $function->signature->name;
374
375     my $domain = $interface->name;
376     my @argsFiltered = grep($_->direction eq "out", @{$function->parameters}); # just keep only out parameters for frontend interface.
377     map($frontendTypes{$_->type} = 1, @argsFiltered); # register required types.
378     my $arguments = join(", ", map(typeTraits($_->type, "param") . " " . $_->name, @argsFiltered)); # prepare arguments for function signature.
379
380     my $signature = "        void ${functionName}(${arguments});";
381     !$agent->{methodSignatures}->{$signature} || die "Duplicate frontend function was detected for signature '$signature'.";
382     $agent->{methodSignatures}->{$signature} = 1;
383     push(@{$agent->{methodDeclarations}}, $signature);
384
385     my @function;
386     push(@function, "void ${frontendClassName}::${domain}::${functionName}(${arguments})");
387     push(@function, "{");
388     push(@function, "    RefPtr<InspectorObject> ${functionName}Message = InspectorObject::create();");
389     push(@function, "    ${functionName}Message->setString(\"type\", \"event\");");
390     push(@function, "    ${functionName}Message->setString(\"domain\", \"$domain\");");
391     push(@function, "    ${functionName}Message->setString(\"event\", \"$functionName\");");
392     push(@function, "    RefPtr<InspectorObject> bodyObject = InspectorObject::create();");
393     my @pushArguments = map("    bodyObject->set" . typeTraits($_->type, "JSONType") . "(\"" . $_->name . "\", " . $_->name . ");", @argsFiltered);
394     push(@function, @pushArguments);
395     push(@function, "    ${functionName}Message->setObject(\"body\", bodyObject);");
396     push(@function, "    m_inspectorClient->sendMessageToFrontend(${functionName}Message->toJSONString());");
397     push(@function, "}");
398     push(@function, "");
399     push(@frontendMethodsImpl, @function);
400 }
401
402 sub generateDocumentationEvent
403 {
404     my $interface = shift;
405     my $function = shift;
406
407     my $functionName = $function->signature->name;
408     my $domain = $interface->name;
409
410     my @argsFiltered = grep($_->direction eq "out", @{$function->parameters});
411
412     my @lines;
413     push(@lines, "<h4>" . $interface->name . "." . ${functionName} . "</h4>");
414     my $doc = $function->signature->extendedAttributes->{"doc"};
415     if ($doc) {
416         push(@lines, $doc);
417     }
418
419     push(@lines, "<pre style='background: lightGrey; padding: 10px'>");
420     push(@lines, "{");
421     push(@lines, "    seq: &lt;number&gt;,");
422     push(@lines, "    type: \"event\",");
423     push(@lines, "    domain: \"$domain\",");
424     if (scalar(@argsFiltered)) {
425         push(@lines, "    event: \"${functionName}\",");
426         push(@lines, "    data: {");
427         my @parameters;
428         foreach my $parameter (@argsFiltered) {
429             push(@parameters, "        " . parameterDocLine($parameter));
430         }
431         push(@lines, join(",\n", @parameters));
432         push(@lines, "    }");
433     } else {
434         push(@lines, "    event: \"${functionName}\"");
435     }
436     push(@lines, "}");
437     push(@lines, "</pre>");
438     push(@documentationLines, @lines);
439 }
440
441 sub camelCase
442 {
443     my $value = shift;
444     $value =~ s/\b(\w)/\U$1/g; # make a camel-case name for type name
445     $value =~ s/ //g;
446     return $value;
447 }
448
449 sub generateBackendFunction
450 {
451     my $interface = shift;
452     my $function = shift;
453
454     my $functionName = $function->signature->name;
455     my $fullQualifiedFunctionName = $interface->name . "_" . $function->signature->name;
456
457     push(@backendConstantDeclarations, "    static const char* ${fullQualifiedFunctionName}Cmd;");
458     push(@backendConstantDefinitions, "const char* ${backendClassName}::${fullQualifiedFunctionName}Cmd = \"${fullQualifiedFunctionName}\";");
459
460     map($backendTypes{$_->type} = 1, @{$function->parameters}); # register required types
461     my @inArgs = grep($_->direction eq "in" && !($_->name eq "callId") , @{$function->parameters});
462     my @outArgs = grep($_->direction eq "out", @{$function->parameters});
463     
464     my $signature = "    void ${fullQualifiedFunctionName}(long callId, InspectorObject* requestMessageObject);";
465     !$backendMethodSignatures{${signature}} || die "Duplicate function was detected for signature '$signature'.";
466     $backendMethodSignatures{${signature}} = "$fullQualifiedFunctionName";
467     push(@backendMethods, ${signature});
468
469     my @function;
470     my $requestMessageObject = scalar(@inArgs) ? " requestMessageObject" : "";
471     push(@function, "void ${backendClassName}::${fullQualifiedFunctionName}(long callId, InspectorObject*$requestMessageObject)");
472     push(@function, "{");
473     push(@function, "    RefPtr<InspectorArray> protocolErrors = InspectorArray::create();");
474     push(@function, "");
475
476     my $domain = $interface->name;
477     my $domainAccessor = typeTraits($domain, "domainAccessor");
478     $backendTypes{$domain} = 1;
479     push(@function, "    if (!$domainAccessor)");
480     push(@function, "        protocolErrors->pushString(\"Protocol Error: $domain handler is not available.\");");
481     push(@function, "");
482
483     # declare local variables for out arguments.
484     push(@function, map("    " . typeTraits($_->type, "variable") . " " . $_->name . " = " . typeTraits($_->type, "defaultValue") . ";", @outArgs));
485
486     my $indent = "";
487     if (scalar(@inArgs)) {
488         push(@function, "    if (RefPtr<InspectorObject> argumentsContainer = requestMessageObject->getObject(\"arguments\")) {");
489
490         foreach my $parameter (@inArgs) {
491             my $name = $parameter->name;
492             my $type = $parameter->type;
493             my $typeString = camelCase($parameter->type);
494             push(@function, "        " . typeTraits($type, "variable") . " $name = get$typeString(argumentsContainer.get(), \"$name\", protocolErrors.get());");
495         }
496         push(@function, "");
497         $indent = "    ";
498     }
499
500     push(@function, "$indent    ErrorString error;");
501     my $args = join(", ", ("&error", map($_->name, @inArgs), map("&" . $_->name, @outArgs)));
502     push(@function, "$indent    if (!protocolErrors->length())");
503     push(@function, "$indent        $domainAccessor->$functionName($args);");
504     push(@function, "$indent    if (error.length())");
505     push(@function, "$indent        protocolErrors->pushString(error);");
506     if (scalar(@inArgs)) {
507         push(@function, "    } else {");
508         push(@function, "        protocolErrors->pushString(\"Protocol Error: 'arguments' property with type 'object' was not found.\");");
509         push(@function, "    }");
510     }
511
512     push(@function, "    // use InspectorFrontend as a marker of WebInspector availability");
513     push(@function, "    if ((callId || protocolErrors->length()) && m_inspectorAgent->hasFrontend()) {");
514     push(@function, "        RefPtr<InspectorObject> responseMessage = InspectorObject::create();");
515     push(@function, "        responseMessage->setNumber(\"seq\", callId);");
516     push(@function, "        responseMessage->setString(\"domain\", \"$domain\");");
517     push(@function, "        responseMessage->setBoolean(\"success\", !protocolErrors->length());");
518     push(@function, "");
519     push(@function, "        if (protocolErrors->length())");
520     push(@function, "            responseMessage->setArray(\"errors\", protocolErrors);");
521     if (scalar(@outArgs)) {
522         push(@function, "        else {");
523         push(@function, "            RefPtr<InspectorObject> responseBody = InspectorObject::create();");
524         push(@function, map("            responseBody->set" . typeTraits($_->type, "JSONType") . "(\"" . $_->name . "\", " . $_->name . ");", @outArgs));
525         push(@function, "            responseMessage->setObject(\"body\", responseBody);");
526         push(@function, "        }");
527     }
528     push(@function, "        m_inspectorAgent->inspectorClient()->sendMessageToFrontend(responseMessage->toJSONString());");
529     push(@function, "    }");
530
531
532     push(@function, "}");
533     push(@function, "");
534     push(@backendMethodsImpl, @function);
535 }
536
537 sub generateDocumentationCommand
538 {
539     my $interface = shift;
540     my $function = shift;
541
542     my $functionName = $function->signature->name;
543     my $domain = $interface->name;
544
545     my @lines;
546
547     push(@lines, "<h4>" . $interface->name . "." . ${functionName} . "</h4>");
548     my $doc = $function->signature->extendedAttributes->{"doc"};
549     if ($doc) {
550         push(@lines, $doc);
551     }
552
553     my @inArgs = grep($_->direction eq "in" && !($_->name eq "callId") , @{$function->parameters});
554     push(@lines, "<pre style='background: lightGrey; padding: 10px'>");
555     push(@lines, "request: {");
556     push(@lines, "    seq: &lt;number&gt;,");
557     push(@lines, "    type: \"request\",");
558     push(@lines, "    domain: \"" . $interface->name . "\",");
559     if (scalar(@inArgs)) {
560         push(@lines, "    command: \"${functionName}\",");
561         push(@lines, "    arguments: {");
562         my @parameters;
563         foreach my $parameter (@inArgs) {
564             push(@parameters, "        " . parameterDocLine($parameter));
565         }
566         push(@lines, join(",\n", @parameters));
567         push(@lines, "    }");
568     } else {
569         push(@lines, "    command: \"${functionName}\"");
570     }
571     push(@lines, "}");
572
573     my @outArgs = grep($_->direction eq "out", @{$function->parameters});    
574     push(@lines, "");
575     push(@lines, "response: {");
576     push(@lines, "    seq: &lt;number&gt;,");
577     if (scalar(@outArgs)) {
578         push(@lines, "    type: \"response\",");
579         push(@lines, "    body: {");
580             my @parameters;
581             foreach my $parameter (@outArgs) {
582                 push(@parameters, "        " . parameterDocLine($parameter));
583             }
584             push(@lines, join(",\n", @parameters));
585         push(@lines, "    }");
586     } else {
587         push(@lines, "    type: \"response\"");
588     }
589     push(@lines, "}");
590     push(@lines, "</pre>");
591
592     push(@documentationLines, @lines);
593 }
594
595 sub generateBackendReportProtocolError
596 {
597     my $reportProtocolError = << "EOF";
598
599 void ${backendClassName}::reportProtocolError(const long callId, const String& errorText) const
600 {
601     RefPtr<InspectorObject> message = InspectorObject::create();
602     message->setNumber("seq", callId);
603     message->setBoolean("success", false);
604     RefPtr<InspectorArray> errors = InspectorArray::create();
605     errors->pushString(errorText);
606     message->setArray("errors", errors);
607     m_inspectorAgent->inspectorClient()->sendMessageToFrontend(message->toJSONString());
608 }
609 EOF
610     return split("\n", $reportProtocolError);
611 }
612
613 sub generateArgumentGetters
614 {
615     my $type = shift;
616     my $json = typeTraits($type, "JSONType");
617     my $variable = typeTraits($type, "variable");
618     my $defaultValue = typeTraits($type, "defaultValue");
619     my $return  = typeTraits($type, "return") ? typeTraits($type, "return") : typeTraits($type, "param");
620
621     my $typeString = camelCase($type);
622     push(@backendConstantDeclarations, "    $return get$typeString(InspectorObject* object, const String& name, InspectorArray* protocolErrors);");
623     my $getterBody = << "EOF";
624
625 $return InspectorBackendDispatcher::get$typeString(InspectorObject* object, const String& name, InspectorArray* protocolErrors)
626 {
627     ASSERT(object);
628     ASSERT(protocolErrors);
629
630     $variable value = $defaultValue;
631     InspectorObject::const_iterator end = object->end();
632     InspectorObject::const_iterator valueIterator = object->find(name);
633
634     if (valueIterator == end)
635         protocolErrors->pushString(String::format("Protocol Error: Argument '\%s' with type '$json' was not found.", name.utf8().data()));
636     else {
637         if (!valueIterator->second->as$json(&value))
638             protocolErrors->pushString(String::format("Protocol Error: Argument '\%s' has wrong type. It should be '$json'.", name.utf8().data()));
639     }
640     return value;
641 }
642 EOF
643
644     return split("\n", $getterBody);
645 }
646
647 sub generateBackendDispatcher
648 {
649     my @body;
650     my @mapEntries = map("        dispatchMap.add(${_}Cmd, &${backendClassName}::$_);", map ($backendMethodSignatures{$_}, @backendMethods));
651     my $mapEntries = join("\n", @mapEntries);
652
653     my $backendDispatcherBody = << "EOF";
654 void ${backendClassName}::dispatch(const String& message)
655 {
656     typedef void (${backendClassName}::*CallHandler)(long callId, InspectorObject* messageObject);
657     typedef HashMap<String, CallHandler> DispatchMap;
658     DEFINE_STATIC_LOCAL(DispatchMap, dispatchMap, );
659     long callId = 0;
660
661     if (dispatchMap.isEmpty()) {
662 $mapEntries
663     }
664
665     RefPtr<InspectorValue> parsedMessage = InspectorValue::parseJSON(message);
666     if (!parsedMessage) {
667         reportProtocolError(callId, "Protocol Error: Invalid message format. Message should be in JSON format.");
668         return;
669     }
670
671     RefPtr<InspectorObject> messageObject = parsedMessage->asObject();
672     if (!messageObject) {
673         reportProtocolError(callId, "Protocol Error: Invalid message format. The message should be a JSONified object.");
674         return;
675     }
676
677     RefPtr<InspectorValue> commandValue = messageObject->get("command");
678     if (!commandValue) {
679         reportProtocolError(callId, "Protocol Error: Invalid message format. 'command' property wasn't found.");
680         return;
681     }
682
683     String command;
684     if (!commandValue->asString(&command)) {
685         reportProtocolError(callId, "Protocol Error: Invalid message format. The type of 'command' property should be string.");
686         return;
687     }
688
689     RefPtr<InspectorValue> domainValue = messageObject->get("domain");
690     if (!domainValue) {
691         reportProtocolError(callId, "Protocol Error: Invalid message format. 'domain' property wasn't found.");
692         return;
693     }
694
695     String domain;
696     if (!domainValue->asString(&domain)) {
697         reportProtocolError(callId, "Protocol Error: Invalid message format. The type of 'domain' property should be string.");
698         return;
699     }
700
701     RefPtr<InspectorValue> callIdValue = messageObject->get("seq");
702     if (!callIdValue) {
703         reportProtocolError(callId, "Protocol Error: Invalid message format. 'seq' property was not found in the request.");
704         return;
705     }
706
707     if (!callIdValue->asNumber(&callId)) {
708         reportProtocolError(callId, "Protocol Error: Invalid message format. The type of 'seq' property should be number.");
709         return;
710     }
711
712     HashMap<String, CallHandler>::iterator it = dispatchMap.find(makeString(domain, "_", command));
713     if (it == dispatchMap.end()) {
714         reportProtocolError(callId, makeString("Protocol Error: Invalid command was received. '", command, "' wasn't found in domain ", domain, "."));
715         return;
716     }
717
718     ((*this).*it->second)(callId, messageObject.get());
719 }
720 EOF
721     return split("\n", $backendDispatcherBody);
722 }
723
724 sub generateBackendMessageParser
725 {
726     my $messageParserBody = << "EOF";
727 bool ${backendClassName}::getCommandName(const String& message, String* result)
728 {
729     RefPtr<InspectorValue> value = InspectorValue::parseJSON(message);
730     if (!value)
731         return false;
732
733     RefPtr<InspectorObject> object = value->asObject();
734     if (!object)
735         return false;
736
737     RefPtr<InspectorValue> commandValue = object->get("command");
738     if (!commandValue)
739         return false;
740
741     return commandValue->asString(result);
742 }
743 EOF
744     return split("\n", $messageParserBody);
745 }
746
747 sub collectBackendJSStubFunctions
748 {
749     my $interface = shift;
750     my @functions = grep(!$_->signature->extendedAttributes->{"event"}, @{$interface->functions});
751     my $domain = $interface->name;
752
753     foreach my $function (@functions) {
754         my $name = $function->signature->name;
755         my $argumentNames = join(",", map("\"" . $_->name . "\": \"" . typeTraits($_->type, "JSType") . "\"", grep($_->direction eq "in", @{$function->parameters})));
756         push(@backendJSStubs, "    this._registerDelegate('{" .
757             "\"seq\": 0, " .
758             "\"domain\": \"$domain\", " .
759             "\"command\": \"$name\", " .
760             "\"arguments\": {$argumentNames}" .
761         "}');");
762     }
763 }
764
765 sub generateBackendStubJS
766 {
767     my $JSStubs = join("\n", @backendJSStubs);
768     my $inspectorBackendStubJS = << "EOF";
769 $licenseTemplate
770
771 InspectorBackendStub = function()
772 {
773     this._lastCallbackId = 1;
774     this._pendingResponsesCount = 0;
775     this._callbacks = {};
776     this._domainDispatchers = {};
777 $JSStubs
778 }
779
780 InspectorBackendStub.prototype = {
781     _wrap: function(callback)
782     {
783         var callbackId = this._lastCallbackId++;
784         ++this._pendingResponsesCount;
785         this._callbacks[callbackId] = callback || function() {};
786         return callbackId;
787     },
788
789     _registerDelegate: function(commandInfo)
790     {
791         var commandObject = JSON.parse(commandInfo);
792         var agentName = commandObject.domain + "Agent";
793         if (!window[agentName])
794             window[agentName] = {};
795         window[agentName][commandObject.command] = this.sendMessageToBackend.bind(this, commandInfo);
796     },
797
798     sendMessageToBackend: function()
799     {
800         var args = Array.prototype.slice.call(arguments);
801         var request = JSON.parse(args.shift());
802
803         for (var key in request.arguments) {
804             if (args.length === 0) {
805                 console.error("Protocol Error: Invalid number of arguments for '" + request.domain + "Agent." + request.command + "' call. It should have the next arguments '" + JSON.stringify(request.arguments) + "'.");
806                 return;
807             }
808             var value = args.shift();
809             if (request.arguments[key] && typeof value !== request.arguments[key]) {
810                 console.error("Protocol Error: Invalid type of argument '" + key + "' for '" + request.domain + "Agent." + request.command + "' call. It should be '" + request.arguments[key] + "' but it is '" + typeof value + "'.");
811                 return;
812             }
813             request.arguments[key] = value;
814         }
815
816         var callback;
817         if (args.length === 1) {
818             if (typeof args[0] !== "function" && typeof args[0] !== "undefined") {
819                 console.error("Protocol Error: Optional callback argument for '" + request.domain + "Agent." + request.command + "' call should be a function but its type is '" + typeof args[0] + "'.");
820                 return;
821             }
822             callback = args[0];
823         }
824         request.seq = this._wrap(callback || function() {});
825
826         if (window.dumpInspectorProtocolMessages)
827             console.log("frontend: " + JSON.stringify(request));
828
829         var message = JSON.stringify(request);
830         InspectorFrontendHost.sendMessageToBackend(message);
831     },
832
833     registerDomainDispatcher: function(domain, dispatcher)
834     {
835         this._domainDispatchers[domain] = dispatcher;
836     },
837
838     dispatch: function(message)
839     {
840         if (window.dumpInspectorProtocolMessages)
841             console.log("backend: " + ((typeof message === "string") ? message : JSON.stringify(message)));
842
843         var messageObject = (typeof message === "string") ? JSON.parse(message) : message;
844
845         var arguments = [];
846         if (messageObject.body)
847             for (var key in messageObject.body)
848                 arguments.push(messageObject.body[key]);
849
850         if ("seq" in messageObject) { // just a response for some request
851             if (messageObject.success)
852                 this._callbacks[messageObject.seq].apply(null, arguments);
853             else
854                 this.reportProtocolError(messageObject);
855
856             --this._pendingResponsesCount;
857             delete this._callbacks[messageObject.seq];
858
859             if (this._scripts && !this._pendingResponsesCount)
860                 this.runAfterPendingDispatches();
861
862             return;
863         }
864
865         if (messageObject.type === "event") {
866             if (!(messageObject.domain in this._domainDispatchers)) {
867                 console.error("Protocol Error: the message is for non-existing domain '" + messageObject.domain + "'");
868                 return;
869             }
870             var dispatcher = this._domainDispatchers[messageObject.domain];
871             if (!(messageObject.event in dispatcher)) {
872                 console.error("Protocol Error: Attempted to dispatch an unimplemented method '" + messageObject.domain + "." + messageObject.event + "'");
873                 return;
874             }
875             dispatcher[messageObject.event].apply(dispatcher, arguments);
876         }
877     },
878
879     reportProtocolError: function(messageObject)
880     {
881         console.error("Protocol Error: InspectorBackend request with seq = " + messageObject.seq + " failed.");
882         for (var i = 0; i < messageObject.errors.length; ++i)
883             console.error("    " + messageObject.errors[i]);
884     },
885
886     runAfterPendingDispatches: function(script)
887     {
888         if (!this._scripts)
889             this._scripts = [];
890
891         if (script)
892             this._scripts.push(script);
893
894         if (!this._pendingResponsesCount) {
895             var scripts = this._scripts;
896             this._scripts = []
897             for (var id = 0; id < scripts.length; ++id)
898                  scripts[id].call(this);
899         }
900     }
901 }
902
903 InspectorBackend = new InspectorBackendStub();
904
905 EOF
906     return split("\n", $inspectorBackendStubJS);
907 }
908
909 sub generateHeader
910 {
911     my $className = shift;
912     my $types = shift;
913     my $constructor = shift;
914     my $constants = shift;
915     my $methods = shift;
916     my $footer = shift;
917
918     my $forwardHeaders = join("\n", sort(map("#include <" . typeTraits($_, "forwardHeader") . ">", grep(typeTraits($_, "forwardHeader"), keys %{$types}))));
919     my $forwardDeclarations = join("\n", sort(map("class " . typeTraits($_, "forward") . ";", grep(typeTraits($_, "forward"), keys %{$types}))));
920     my $constantDeclarations = join("\n", @{$constants});
921     my $methodsDeclarations = join("\n", @{$methods});
922
923     my $headerBody = << "EOF";
924 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
925 // Use of this source code is governed by a BSD-style license that can be
926 // found in the LICENSE file.
927 #ifndef ${className}_h
928 #define ${className}_h
929
930 ${forwardHeaders}
931
932 namespace $namespace {
933
934 $forwardDeclarations
935
936 typedef String ErrorString;
937
938 class $className {
939 public:
940 $constructor
941
942 $constantDeclarations
943 $methodsDeclarations
944
945 $footer
946 };
947
948 } // namespace $namespace
949 #endif // !defined(${className}_h)
950
951 EOF
952     return $headerBody;
953 }
954
955 sub generateSource
956 {
957     my $className = shift;
958     my $types = shift;
959     my $constants = shift;
960     my $methods = shift;
961
962     my @sourceContent = split("\r", $licenseTemplate);
963     push(@sourceContent, "\n#include \"config.h\"");
964     push(@sourceContent, "#include \"$className.h\"");
965     push(@sourceContent, "#include <wtf/text/StringConcatenate.h>");
966     push(@sourceContent, "#include <wtf/text/CString.h>");
967     push(@sourceContent, "");
968     push(@sourceContent, "#if ENABLE(INSPECTOR)");
969     push(@sourceContent, "");
970
971     my %headers;
972     foreach my $type (keys %{$types}) {
973         $headers{"#include \"" . typeTraits($type, "header") . "\""} = 1 if !typeTraits($type, "header") eq  "";
974     }
975     push(@sourceContent, sort keys %headers);
976     push(@sourceContent, "");
977     push(@sourceContent, "namespace $namespace {");
978     push(@sourceContent, "");
979     push(@sourceContent, join("\n", @{$constants}));
980     push(@sourceContent, "");
981     push(@sourceContent, @{$methods});
982     push(@sourceContent, "");
983     push(@sourceContent, "} // namespace $namespace");
984     push(@sourceContent, "");
985     push(@sourceContent, "#endif // ENABLE(INSPECTOR)");
986     push(@sourceContent, "");
987     return @sourceContent;
988 }
989
990 sub typeTraits
991 {
992     my $type = shift;
993     my $trait = shift;
994     return $typeTransform{$type}->{$trait};
995 }
996
997 sub parameterDocType
998 {
999     my $parameter = shift;
1000     my $subtype = $parameter->extendedAttributes->{"type"};
1001     if ($subtype) {
1002         my $pattern = typeTraits($parameter->type, "DocType");
1003         return sprintf($pattern, "&lt;$subtype&gt;");
1004     }
1005
1006     my $subtypeRef = $parameter->extendedAttributes->{"typeRef"};
1007     if ($subtypeRef) {
1008         my $pattern = typeTraits($parameter->type, "DocType");
1009         return sprintf($pattern, "&lt;<a href='#$subtypeRef'>" . $subtypeRef . "</a>&gt;");
1010     }
1011
1012     return "&lt;" . typeTraits($parameter->type, "JSType") . "&gt;";
1013 }
1014
1015 sub parameterDocLine
1016 {
1017     my $parameter = shift;
1018
1019     my $result = $parameter->name . ": " . parameterDocType($parameter);
1020     my $doc = $parameter->extendedAttributes->{"doc"};
1021     if ($doc) {
1022         $result = $result . " // " . $doc;
1023     }
1024     return $result;
1025 }
1026
1027 sub finish
1028 {
1029     my $object = shift;
1030
1031     push(@backendMethodsImpl, generateBackendDispatcher());
1032     push(@backendMethodsImpl, generateBackendReportProtocolError());
1033     unshift(@frontendMethodsImpl, generateFrontendConstructorImpl(), "");
1034
1035     open(my $SOURCE, ">$outputDir/$frontendClassName.cpp") || die "Couldn't open file $outputDir/$frontendClassName.cpp";
1036     print $SOURCE join("\n", generateSource($frontendClassName, \%frontendTypes, \@frontendConstantDefinitions, \@frontendMethodsImpl));
1037     close($SOURCE);
1038     undef($SOURCE);
1039
1040     open(my $HEADER, ">$outputHeadersDir/$frontendClassName.h") || die "Couldn't open file $outputHeadersDir/$frontendClassName.h";
1041     print $HEADER generateHeader($frontendClassName, \%frontendTypes, $frontendConstructor, \@frontendConstantDeclarations, \@frontendMethods, join("\n", @frontendFooter));
1042     close($HEADER);
1043     undef($HEADER);
1044
1045     # Make dispatcher methods private on the backend.
1046     push(@backendConstantDeclarations, "");
1047     push(@backendConstantDeclarations, "private:");
1048
1049     foreach my $type (keys %backendTypes) {
1050         if (typeTraits($type, "JSONType")) {
1051             push(@backendMethodsImpl, generateArgumentGetters($type));
1052         }
1053     }
1054
1055     push(@backendMethodsImpl, generateBackendMessageParser());
1056     push(@backendMethodsImpl, "");
1057
1058     push(@backendConstantDeclarations, "");
1059
1060     open($SOURCE, ">$outputDir/$backendClassName.cpp") || die "Couldn't open file $outputDir/$backendClassName.cpp";
1061     print $SOURCE join("\n", generateSource($backendClassName, \%backendTypes, \@backendConstantDefinitions, \@backendMethodsImpl));
1062     close($SOURCE);
1063     undef($SOURCE);
1064
1065     open($HEADER, ">$outputHeadersDir/$backendClassName.h") || die "Couldn't open file $outputHeadersDir/$backendClassName.h";
1066     print $HEADER join("\n", generateHeader($backendClassName, \%backendTypes, $backendConstructor, \@backendConstantDeclarations, \@backendMethods, $backendFooter));
1067     close($HEADER);
1068     undef($HEADER);
1069
1070     open(my $JS_STUB, ">$outputDir/$backendJSStubName.js") || die "Couldn't open file $outputDir/$backendJSStubName.js";
1071     print $JS_STUB join("\n", generateBackendStubJS());
1072     close($JS_STUB);
1073     undef($JS_STUB);
1074
1075     open(my $DOCS, ">$outputDir/WebInspectorProtocol.html") || die "Couldn't open file $outputDir/WebInspectorProtocol.html";
1076     print $DOCS "<ol class='toc' style='list-style: none; padding: 0'>";
1077     print $DOCS join("\n", @documentationToc);
1078     print $DOCS "</ol>";
1079     print $DOCS join("\n", @documentationLines);
1080     close($DOCS);
1081     undef($DOCS);
1082 }
1083
1084 1;