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