Web Inspector: It'd be useful to have performance stats for the back-end to front...
[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_applicationCacheAgent",
17 };
18 $typeTransform{"CSS"} = {
19     "forward" => "InspectorCSSAgent",
20     "header" => "InspectorCSSAgent.h",
21     "domainAccessor" => "m_cssAgent",
22 };
23 $typeTransform{"Console"} = {
24     "forward" => "InspectorConsoleAgent",
25     "header" => "InspectorConsoleAgent.h",
26     "domainAccessor" => "m_consoleAgent",
27 };
28 $typeTransform{"Page"} = {
29     "forward" => "InspectorPageAgent",
30     "header" => "InspectorPageAgent.h",
31     "domainAccessor" => "m_pageAgent",
32 };
33 $typeTransform{"Debugger"} = {
34     "forward" => "InspectorDebuggerAgent",
35     "header" => "InspectorDebuggerAgent.h",
36     "domainAccessor" => "m_debuggerAgent",
37 };
38 $typeTransform{"DOMDebugger"} = {
39     "forward" => "InspectorDOMDebuggerAgent",
40     "header" => "InspectorDOMDebuggerAgent.h",
41     "domainAccessor" => "m_domDebuggerAgent",
42 };
43 $typeTransform{"Database"} = {
44     "forward" => "InspectorDatabaseAgent",
45     "header" => "InspectorDatabaseAgent.h",
46     "domainAccessor" => "m_databaseAgent",
47 };
48 $typeTransform{"DOM"} = {
49     "forward" => "InspectorDOMAgent",
50     "header" => "InspectorDOMAgent.h",
51     "domainAccessor" => "m_domAgent",
52 };
53 $typeTransform{"DOMStorage"} = {
54     "forward" => "InspectorDOMStorageAgent",
55     "header" => "InspectorDOMStorageAgent.h",
56     "domainAccessor" => "m_domStorageAgent",
57 };
58 $typeTransform{"FileSystem"} = {
59     "forward" => "InspectorFileSystemAgent",
60     "header" => "InspectorFileSystemAgent.h",
61     "domainAccessor" => "m_fileSystemAgent",
62 };
63 $typeTransform{"Inspector"} = {
64     "forward" => "InspectorAgent",
65     "header" => "InspectorAgent.h",
66     "domainAccessor" => "m_inspectorAgent",
67 };
68 $typeTransform{"Network"} = {
69     "forward" => "InspectorResourceAgent",
70     "header" => "InspectorResourceAgent.h",
71     "domainAccessor" => "m_resourceAgent",
72 };
73 $typeTransform{"Profiler"} = {
74     "forward" => "InspectorProfilerAgent",
75     "header" => "InspectorProfilerAgent.h",
76     "domainAccessor" => "m_profilerAgent",
77 };
78 $typeTransform{"Runtime"} = {
79     "forward" => "InspectorRuntimeAgent",
80     "header" => "InspectorRuntimeAgent.h",
81     "domainAccessor" => "m_runtimeAgent",
82 };
83 $typeTransform{"Timeline"} = {
84     "forward" => "InspectorTimelineAgent",
85     "header" => "InspectorTimelineAgent.h",
86     "domainAccessor" => "m_timelineAgent",
87 };
88 $typeTransform{"Worker"} = {
89     "forward" => "InspectorWorkerAgent",
90     "header" => "InspectorWorkerAgent.h",
91     "domainAccessor" => "m_workerAgent",
92 };
93
94 $typeTransform{"Frontend"} = {
95     "forward" => "InspectorFrontend",
96     "header" => "InspectorFrontend.h",
97 };
98 $typeTransform{"PassRefPtr"} = {
99     "forwardHeader" => "wtf/PassRefPtr.h",
100 };
101 $typeTransform{"RefCounted"} = {
102     "forwardHeader" => "wtf/RefCounted.h",
103 };
104 $typeTransform{"InspectorFrontendChannel"} = {
105     "forward" => "InspectorFrontendChannel",
106     "header" => "InspectorFrontendChannel.h",
107 };
108 $typeTransform{"Object"} = {
109     "param" => "PassRefPtr<InspectorObject>",
110     "variable" => "RefPtr<InspectorObject>",
111     "defaultValue" => "InspectorObject::create()",
112     "forward" => "InspectorObject",
113     "header" => "InspectorValues.h",
114     "JSONType" => "Object",
115     "JSType" => "object",
116 };
117 $typeTransform{"Array"} = {
118     "param" => "PassRefPtr<InspectorArray>",
119     "variable" => "RefPtr<InspectorArray>",
120     "defaultValue" => "InspectorArray::create()",
121     "forward" => "InspectorArray",
122     "header" => "InspectorValues.h",
123     "JSONType" => "Array",
124     "JSType" => "object",
125 };
126 $typeTransform{"Value"} = {
127     "param" => "PassRefPtr<InspectorValue>",
128     "variable" => "RefPtr<InspectorValue>",
129     "defaultValue" => "InspectorValue::null()",
130     "forward" => "InspectorValue",
131     "header" => "InspectorValues.h",
132     "JSONType" => "Value",
133     "JSType" => "",
134 };
135 $typeTransform{"String"} = {
136     "param" => "const String&",
137     "variable" => "String",
138     "return" => "String",
139     "defaultValue" => "\"\"",
140     "forwardHeader" => "PlatformString.h",
141     "header" => "PlatformString.h",
142     "JSONType" => "String",
143     "JSType" => "string"
144 };
145 $typeTransform{"long"} = {
146     "param" => "long",
147     "variable" => "long",
148     "defaultValue" => "0",
149     "forward" => "",
150     "header" => "",
151     "JSONType" => "Number",
152     "JSType" => "number"
153 };
154 $typeTransform{"int"} = {
155     "param" => "int",
156     "variable" => "int",
157     "defaultValue" => "0",
158     "forward" => "",
159     "header" => "",
160     "JSONType" => "Number",
161     "JSType" => "number"
162 };
163 $typeTransform{"unsigned long"} = {
164     "param" => "unsigned long",
165     "variable" => "unsigned long",
166     "defaultValue" => "0u",
167     "forward" => "",
168     "header" => "",
169     "JSONType" => "Number",
170     "JSType" => "number"
171 };
172 $typeTransform{"unsigned int"} = {
173     "param" => "unsigned int",
174     "variable" => "unsigned int",
175     "defaultValue" => "0u",
176     "forward" => "",
177     "header" => "",
178     "JSONType" => "Number",
179     "JSType" => "number"
180 };
181 $typeTransform{"double"} = {
182     "param" => "double",
183     "variable" => "double",
184     "defaultValue" => "0.0",
185     "forward" => "",
186     "header" => "",
187     "JSONType" => "Number",
188     "JSType" => "number"
189 };
190 $typeTransform{"boolean"} = {
191     "param" => "bool",
192     "variable"=> "bool",
193     "defaultValue" => "false",
194     "forward" => "",
195     "header" => "",
196     "JSONType" => "Boolean",
197     "JSType" => "boolean"
198 };
199 $typeTransform{"void"} = {
200     "forward" => "",
201     "header" => ""
202 };
203 $typeTransform{"Vector"} = {
204     "header" => "wtf/Vector.h"
205 };
206
207 # Default License Templates
208
209 my $licenseTemplate = << "EOF";
210 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
211 // Use of this source code is governed by a BSD-style license that can be
212 // found in the LICENSE file.
213 EOF
214
215 my $codeGenerator;
216 my $outputDir;
217 my $outputHeadersDir;
218 my $writeDependencies;
219 my $verbose;
220
221 my $namespace;
222
223 my $backendClassName;
224 my $backendClassDeclaration;
225 my $backendJSStubName;
226 my %backendTypes;
227 my @backendMethods;
228 my @backendMethodsImpl;
229 my %backendMethodSignatures;
230 my $backendConstructor;
231 my @backendConstantDeclarations;
232 my @backendConstantDefinitions;
233 my @backendFooter;
234 my @backendJSStubs;
235 my @backendJSEvents;
236 my %backendDomains;
237
238 my $frontendClassName;
239 my %frontendTypes;
240 my @frontendMethods;
241 my @frontendAgentFields;
242 my @frontendMethodsImpl;
243 my %frontendMethodSignatures;
244 my $frontendConstructor;
245 my @frontendConstantDeclarations;
246 my @frontendConstantDefinitions;
247 my @frontendFooter;
248 my @frontendDomains;
249
250 # Default constructor
251 sub new
252 {
253     my $object = shift;
254     my $reference = { };
255
256     $codeGenerator = shift;
257     $outputDir = shift;
258     $outputHeadersDir = shift;
259     shift; # $useLayerOnTop
260     shift; # $preprocessor
261     $writeDependencies = shift;
262     $verbose = shift;
263
264     bless($reference, $object);
265     return $reference;
266 }
267
268 # Params: 'idlDocument' struct
269 sub GenerateModule
270 {
271     my $object = shift;
272     my $dataNode = shift;
273
274     $namespace = $dataNode->module;
275     $namespace =~ s/core/WebCore/;
276
277     $frontendClassName = "InspectorFrontend";
278     $frontendConstructor = "    ${frontendClassName}(InspectorFrontendChannel*);";
279     push(@frontendFooter, "private:");
280     push(@frontendFooter, "    InspectorFrontendChannel* m_inspectorFrontendChannel;");
281     $frontendTypes{"String"} = 1;
282     $frontendTypes{"InspectorFrontendChannel"} = 1;
283     $frontendTypes{"PassRefPtr"} = 1;
284
285     $backendClassName = "InspectorBackendDispatcher";
286     $backendClassDeclaration = "InspectorBackendDispatcher: public RefCounted<InspectorBackendDispatcher>";
287     $backendJSStubName = "InspectorBackendStub";
288     $backendTypes{"Inspector"} = 1;
289     $backendTypes{"InspectorFrontendChannel"} = 1;
290     $backendTypes{"PassRefPtr"} = 1;
291     $backendTypes{"RefCounted"} = 1;
292     $backendTypes{"Object"} = 1;
293 }
294
295 # Params: 'idlDocument' struct
296 sub GenerateInterface
297 {
298     my $object = shift;
299     my $interface = shift;
300     my $defines = shift;
301
302     my %agent = (
303         methodDeclarations => [],
304         methodSignatures => {}
305     );
306     generateFunctions($interface, \%agent);
307     if (@{$agent{methodDeclarations}}) {
308         push(@frontendDomains, $interface->name);
309         generateAgentDeclaration($interface, \%agent);
310     }
311 }
312
313 sub generateAgentDeclaration
314 {
315     my $interface = shift;
316     my $agent = shift;
317     my $agentName = $interface->name;
318     push(@frontendMethods, "    class ${agentName} {");
319     push(@frontendMethods, "    public:");
320     push(@frontendMethods, "        ${agentName}(InspectorFrontendChannel* inspectorFrontendChannel) : m_inspectorFrontendChannel(inspectorFrontendChannel) { }");
321     push(@frontendMethods, @{$agent->{methodDeclarations}});
322     push(@frontendMethods, "        void setInspectorFrontendChannel(InspectorFrontendChannel* inspectorFrontendChannel) { m_inspectorFrontendChannel = inspectorFrontendChannel; }");
323     push(@frontendMethods, "        InspectorFrontendChannel* getInspectorFrontendChannel() { return m_inspectorFrontendChannel; }");
324     push(@frontendMethods, "    private:");
325     push(@frontendMethods, "        InspectorFrontendChannel* m_inspectorFrontendChannel;");
326     push(@frontendMethods, "    };");
327     push(@frontendMethods, "");
328
329     my $getterName = lc($agentName);
330     push(@frontendMethods, "    ${agentName}* ${getterName}() { return &m_${getterName}; }");
331     push(@frontendMethods, "");
332
333     push(@frontendFooter, "    ${agentName} m_${getterName};");
334
335     push(@frontendAgentFields, "m_${getterName}");
336 }
337
338 sub generateFrontendConstructorImpl
339 {
340     my @frontendConstructorImpl;
341     push(@frontendConstructorImpl, "${frontendClassName}::${frontendClassName}(InspectorFrontendChannel* inspectorFrontendChannel)");
342     push(@frontendConstructorImpl, "    : m_inspectorFrontendChannel(inspectorFrontendChannel)");
343     foreach my $agentField (@frontendAgentFields) {
344         push(@frontendConstructorImpl, "    , ${agentField}(inspectorFrontendChannel)");
345     }
346     push(@frontendConstructorImpl, "{");
347     push(@frontendConstructorImpl, "}");
348     return @frontendConstructorImpl;
349 }
350
351 sub generateFunctions
352 {
353     my $interface = shift;
354     my $agent = shift;
355
356     foreach my $function (@{$interface->functions}) {
357         if ($function->signature->extendedAttributes->{"event"}) {
358             generateFrontendFunction($interface, $function, $agent);
359         } else {
360             generateBackendFunction($interface, $function);
361         }
362     }
363
364     collectBackendJSStubFunctions($interface);
365     collectBackendJSStubEvents($interface);
366 }
367
368 sub generateFrontendFunction
369 {
370     my $interface = shift;
371     my $function = shift;
372     my $agent = shift;
373
374     my $functionName = $function->signature->name;
375
376     my $domain = $interface->name;
377     my @argsFiltered = grep($_->direction eq "out", @{$function->parameters}); # just keep only out parameters for frontend interface.
378     map($frontendTypes{$_->type} = 1, @argsFiltered); # register required types.
379     my $arguments = join(", ", map(typeTraits($_->type, "param") . " " . $_->name, @argsFiltered)); # prepare arguments for function signature.
380
381     my $signature = "        void ${functionName}(${arguments});";
382     !$agent->{methodSignatures}->{$signature} || die "Duplicate frontend function was detected for signature '$signature'.";
383     $agent->{methodSignatures}->{$signature} = 1;
384     push(@{$agent->{methodDeclarations}}, $signature);
385
386     my @function;
387     push(@function, "void ${frontendClassName}::${domain}::${functionName}(${arguments})");
388     push(@function, "{");
389     push(@function, "    RefPtr<InspectorObject> ${functionName}Message = InspectorObject::create();");
390     push(@function, "    ${functionName}Message->setString(\"method\", \"$domain.$functionName\");");
391     if (scalar(@argsFiltered)) {
392         push(@function, "    RefPtr<InspectorObject> paramsObject = InspectorObject::create();");
393
394         foreach my $parameter (@argsFiltered) {
395             my $optional = $parameter->extendedAttributes->{"optional"} ? "if (" . $parameter->name . ")\n        " : "";
396             push(@function, "    " . $optional . "paramsObject->set" . typeTraits($parameter->type, "JSONType") . "(\"" . $parameter->name . "\", " . $parameter->name . ");");
397         }
398         push(@function, "    ${functionName}Message->setObject(\"params\", paramsObject);");
399     }
400     push(@function, "    if (m_inspectorFrontendChannel)");
401     push(@function, "        m_inspectorFrontendChannel->sendMessageToFrontend(${functionName}Message->toJSONString());");
402     push(@function, "}");
403     push(@function, "");
404     push(@frontendMethodsImpl, @function);
405 }
406
407 sub camelCase
408 {
409     my $value = shift;
410     $value =~ s/\b(\w)/\U$1/g; # make a camel-case name for type name
411     $value =~ s/ //g;
412     return $value;
413 }
414
415 sub generateBackendFunction
416 {
417     my $interface = shift;
418     my $function = shift;
419
420     my $functionName = $function->signature->name;
421     my $fullQualifiedFunctionName = $interface->name . "_" . $functionName;
422     my $fullQualifiedFunctionNameDot = $interface->name . "." . $functionName;
423
424     push(@backendConstantDeclarations, "        k${fullQualifiedFunctionName}Cmd,");
425     push(@backendConstantDefinitions, "    \"${fullQualifiedFunctionNameDot}\",");
426
427     map($backendTypes{$_->type} = 1, @{$function->parameters}); # register required types
428     my @inArgs = grep($_->direction eq "in", @{$function->parameters});
429     my @outArgs = grep($_->direction eq "out", @{$function->parameters});
430     
431     my $signature = "    void ${fullQualifiedFunctionName}(long callId, InspectorObject* requestMessageObject);";
432     !$backendMethodSignatures{${signature}} || die "Duplicate function was detected for signature '$signature'.";
433     $backendMethodSignatures{${signature}} = "$fullQualifiedFunctionName";
434     push(@backendMethods, ${signature});
435
436     my @function;
437     my $requestMessageObject = scalar(@inArgs) ? " requestMessageObject" : "";
438     push(@function, "void ${backendClassName}::${fullQualifiedFunctionName}(long callId, InspectorObject*$requestMessageObject)");
439     push(@function, "{");
440     push(@function, "    RefPtr<InspectorArray> protocolErrors = InspectorArray::create();");
441     push(@function, "");
442
443     my $domain = $interface->name;
444     my $domainAccessor = typeTraits($domain, "domainAccessor");
445     $backendTypes{$domain} = 1;
446     $backendDomains{$domain} = 1;
447     push(@function, "    if (!$domainAccessor)");
448     push(@function, "        protocolErrors->pushString(\"$domain handler is not available.\");");
449     push(@function, "");
450
451     # declare local variables for out arguments.
452     if (scalar(@outArgs)) {
453         push(@function, map("    " . typeTraits($_->type, "variable") . " out_" . $_->name . " = " . typeTraits($_->type, "defaultValue") . ";", @outArgs));
454         push(@function, "");
455     }
456     push(@function, "    ErrorString error;");
457     push(@function, "");
458
459     my $indent = "";
460     if (scalar(@inArgs)) {
461         push(@function, "    RefPtr<InspectorObject> paramsContainer = requestMessageObject->getObject(\"params\");");
462         push(@function, "    InspectorObject* paramsContainerPtr = paramsContainer.get();");
463         push(@function, "    InspectorArray* protocolErrorsPtr = protocolErrors.get();");
464
465         foreach my $parameter (@inArgs) {
466             my $name = $parameter->name;
467             my $type = $parameter->type;
468             my $typeString = camelCase($parameter->type);
469             my $optionalFlagArgument = "0";
470             if ($parameter->extendedAttributes->{"optional"}) {
471                 push(@function, "    bool ${name}_valueFound = false;");
472                 $optionalFlagArgument = "&${name}_valueFound";
473             }
474             push(@function, "    " . typeTraits($type, "variable") . " in_$name = get$typeString(paramsContainerPtr, \"$name\", $optionalFlagArgument, protocolErrorsPtr);");
475         }
476         push(@function, "");
477         $indent = "    ";
478     }
479
480     my $args = join(", ",
481                     ("&error",
482                      map(($_->extendedAttributes->{"optional"} ?
483                           $_->name . "_valueFound ? &in_" . $_->name . " : 0" :
484                           "in_" . $_->name), @inArgs),
485                      map("&out_" . $_->name, @outArgs)));
486
487     push(@function, $indent . "if (!protocolErrors->length())");
488     push(@function, $indent . "    $domainAccessor->$functionName($args);");
489     push(@function, "");
490     push(@function, "    RefPtr<InspectorObject> result = InspectorObject::create();");
491     if (scalar(@outArgs)) {
492         push(@function, "    if (!protocolErrors->length() && !error.length()) {");
493         foreach my $parameter (@outArgs) {
494             my $offset = "        ";
495             # Don't add optional boolean parameter to the result unless it is "true"
496             if ($parameter->extendedAttributes->{"optional"} && $parameter->type eq "boolean") {
497                 push(@function, $offset . "if (out_" . $parameter->name . ")");
498                 $offset .= "    ";
499             }
500             push(@function, $offset . "result->set" . typeTraits($parameter->type, "JSONType") . "(\"" . $parameter->name . "\", out_" . $parameter->name . ");");
501         }
502         push(@function, "    }");
503     }
504     push(@function, "    sendResponse(callId, result, String::format(\"Some arguments of method '%s' can't be processed\", \"$fullQualifiedFunctionNameDot\"), protocolErrors, error);");
505     push(@function, "}");
506     push(@function, "");
507     push(@backendMethodsImpl, @function);
508 }
509
510 sub generateBackendSendResponse
511 {
512     my $sendResponse = << "EOF";
513
514 void ${backendClassName}::sendResponse(long callId, PassRefPtr<InspectorObject> result, const String& errorMessage, PassRefPtr<InspectorArray> protocolErrors, ErrorString invocationError)
515 {
516     if (protocolErrors->length()) {
517         reportProtocolError(&callId, InvalidParams, errorMessage, protocolErrors);
518         return;
519     }
520     if (invocationError.length()) {
521         reportProtocolError(&callId, ServerError, invocationError);
522         return;
523     }
524
525     RefPtr<InspectorObject> responseMessage = InspectorObject::create();
526     responseMessage->setObject("result", result);
527     responseMessage->setNumber("id", callId);
528     if (m_inspectorFrontendChannel)
529         m_inspectorFrontendChannel->sendMessageToFrontend(responseMessage->toJSONString());
530 }    
531 EOF
532     return split("\n", $sendResponse);
533 }
534
535 sub generateBackendReportProtocolError
536 {
537     my $reportProtocolError = << "EOF";
538
539 void ${backendClassName}::reportProtocolError(const long* const callId, CommonErrorCode code, const String& errorMessage) const
540 {
541     reportProtocolError(callId, code, errorMessage, 0);
542 }
543
544 void ${backendClassName}::reportProtocolError(const long* const callId, CommonErrorCode code, const String& errorMessage, PassRefPtr<InspectorArray> data) const
545 {
546     DEFINE_STATIC_LOCAL(Vector<int>,s_commonErrors,);
547     if (!s_commonErrors.size()) {
548         s_commonErrors.insert(ParseError, -32700);
549         s_commonErrors.insert(InvalidRequest, -32600);
550         s_commonErrors.insert(MethodNotFound, -32601);
551         s_commonErrors.insert(InvalidParams, -32602);
552         s_commonErrors.insert(InternalError, -32603);
553         s_commonErrors.insert(ServerError, -32000);
554     }
555     ASSERT(code >=0);
556     ASSERT((unsigned)code < s_commonErrors.size());
557     ASSERT(s_commonErrors[code]);
558     RefPtr<InspectorObject> error = InspectorObject::create();
559     error->setNumber("code", s_commonErrors[code]);
560     error->setString("message", errorMessage);
561     ASSERT(error);
562     if (data)
563         error->setArray("data", data);
564     RefPtr<InspectorObject> message = InspectorObject::create();
565     message->setObject("error", error);
566     if (callId)
567         message->setNumber("id", *callId);
568     else
569         message->setValue("id", InspectorValue::null());
570     if (m_inspectorFrontendChannel)
571         m_inspectorFrontendChannel->sendMessageToFrontend(message->toJSONString());
572 }
573 EOF
574     return split("\n", $reportProtocolError);
575 }
576
577 sub generateArgumentGetters
578 {
579     my $type = shift;
580     my $json = typeTraits($type, "JSONType");
581     my $variable = typeTraits($type, "variable");
582     my $defaultValue = typeTraits($type, "defaultValue");
583     my $return  = typeTraits($type, "return") ? typeTraits($type, "return") : typeTraits($type, "param");
584
585     my $typeString = camelCase($type);
586     push(@backendConstantDeclarations, "    static $return get$typeString(InspectorObject* object, const String& name, bool* valueFound, InspectorArray* protocolErrors);");
587     my $getterBody = << "EOF";
588
589 $return InspectorBackendDispatcher::get$typeString(InspectorObject* object, const String& name, bool* valueFound, InspectorArray* protocolErrors)
590 {
591     ASSERT(protocolErrors);
592
593     if (valueFound)
594         *valueFound = false;
595
596     $variable value = $defaultValue;
597
598     if (!object) {
599         if (!valueFound) {
600             // Required parameter in missing params container.
601             protocolErrors->pushString(String::format("'params' object must contain required parameter '\%s' with type '$json'.", name.utf8().data()));
602         }
603         return value;
604     }
605
606     InspectorObject::const_iterator end = object->end();
607     InspectorObject::const_iterator valueIterator = object->find(name);
608
609     if (valueIterator == end) {
610         if (!valueFound)
611             protocolErrors->pushString(String::format("Parameter '\%s' with type '$json' was not found.", name.utf8().data()));
612         return value;
613     }
614
615     if (!valueIterator->second->as$json(&value))
616         protocolErrors->pushString(String::format("Parameter '\%s' has wrong type. It must be '$json'.", name.utf8().data()));
617     else
618         if (valueFound)
619             *valueFound = true;
620     return value;
621 }
622 EOF
623
624     return split("\n", $getterBody);
625 }
626
627 sub generateBackendDispatcher
628 {
629     my @body;
630     my @mapEntries = map("        &${backendClassName}::$_,", map ($backendMethodSignatures{$_}, @backendMethods));
631     my $mapEntries = join("\n", @mapEntries);
632
633     my $backendDispatcherBody = << "EOF";
634 void ${backendClassName}::dispatch(const String& message)
635 {
636     RefPtr<${backendClassName}> protect = this;
637     typedef void (${backendClassName}::*CallHandler)(long callId, InspectorObject* messageObject);
638     typedef HashMap<String, CallHandler> DispatchMap;
639     DEFINE_STATIC_LOCAL(DispatchMap, dispatchMap, );
640     long callId = 0;
641
642     if (dispatchMap.isEmpty()) {
643         static CallHandler handlers[] = {
644 $mapEntries
645         };
646         size_t length = sizeof(commandNames) / sizeof(commandNames[0]);
647         for (size_t i = 0; i < length; ++i)
648             dispatchMap.add(commandNames[i], handlers[i]);
649     }
650
651     RefPtr<InspectorValue> parsedMessage = InspectorValue::parseJSON(message);
652     if (!parsedMessage) {
653         reportProtocolError(0, ParseError, "Message must be in JSON format");
654         return;
655     }
656
657     RefPtr<InspectorObject> messageObject = parsedMessage->asObject();
658     if (!messageObject) {
659         reportProtocolError(0, InvalidRequest, "Message must be a JSONified object");
660         return;
661     }
662
663     RefPtr<InspectorValue> callIdValue = messageObject->get("id");
664     if (!callIdValue) {
665         reportProtocolError(0, InvalidRequest, "'id' property was not found");
666         return;
667     }
668
669     if (!callIdValue->asNumber(&callId)) {
670         reportProtocolError(0, InvalidRequest, "The type of 'id' property must be number");
671         return;
672     }
673
674     RefPtr<InspectorValue> methodValue = messageObject->get("method");
675     if (!methodValue) {
676         reportProtocolError(&callId, InvalidRequest, "'method' property wasn't found");
677         return;
678     }
679
680     String method;
681     if (!methodValue->asString(&method)) {
682         reportProtocolError(&callId, InvalidRequest, "The type of 'method' property must be string");
683         return;
684     }
685
686     HashMap<String, CallHandler>::iterator it = dispatchMap.find(method);
687     if (it == dispatchMap.end()) {
688         reportProtocolError(&callId, MethodNotFound, "'" + method + "' wasn't found");
689         return;
690     }
691
692     ((*this).*it->second)(callId, messageObject.get());
693 }
694 EOF
695     return split("\n", $backendDispatcherBody);
696 }
697
698 sub generateBackendMessageParser
699 {
700     my $messageParserBody = << "EOF";
701 bool ${backendClassName}::getCommandName(const String& message, String* result)
702 {
703     RefPtr<InspectorValue> value = InspectorValue::parseJSON(message);
704     if (!value)
705         return false;
706
707     RefPtr<InspectorObject> object = value->asObject();
708     if (!object)
709         return false;
710
711     if (!object->getString("method", result))
712         return false;
713
714     return true;
715 }
716 EOF
717     return split("\n", $messageParserBody);
718 }
719
720 sub collectBackendJSStubFunctions
721 {
722     my $interface = shift;
723     my @functions = grep(!$_->signature->extendedAttributes->{"event"}, @{$interface->functions});
724     my $domain = $interface->name;
725
726     foreach my $function (@functions) {
727         my $name = $function->signature->name;
728         my @inArgs = grep($_->direction eq "in", @{$function->parameters});
729         my $argumentNames = join(
730             ",",
731             map("\"" . $_->name . "\": {"
732                 . "\"optional\": " . ($_->extendedAttributes->{"optional"} ? "true " : "false") . ", "
733                 . "\"type\": \"" . typeTraits($_->type, "JSType") . "\""
734                 . "}",
735                  @inArgs));
736         push(@backendJSStubs, "    this._registerDelegate('{" .
737             "\"method\": \"$domain.$name\", " .
738             (scalar(@inArgs) ? "\"params\": {$argumentNames}, " : "") .
739             "\"id\": 0" .
740         "}');");
741     }
742 }
743
744 sub collectBackendJSStubEvents
745 {
746     my $interface = shift;
747     my @functions = grep($_->signature->extendedAttributes->{"event"}, @{$interface->functions});
748     my $domain = $interface->name;
749
750     foreach my $function (@functions) {
751         my $name = $domain . "." . $function->signature->name;
752         my @outArgs = grep($_->direction eq "out", @{$function->parameters});
753         my $argumentNames = join(",", map("\"" . $_->name . "\"" , @outArgs));
754         push(@backendJSEvents, "    this._eventArgs[\"" . $name . "\"] = [" . $argumentNames ."];");
755     }
756 }
757
758 sub generateBackendStubJS
759 {
760     my $JSRegisterDomainDispatchers = join("\n", map("    this.register" . $_ . "Dispatcher = this._registerDomainDispatcher.bind(this, \"" . $_ ."\");", @frontendDomains));
761     my $JSStubs = join("\n", @backendJSStubs);
762     my $JSEvents = join("\n", @backendJSEvents);
763     my $inspectorBackendStubJS = << "EOF";
764 $licenseTemplate
765
766 InspectorBackendStub = function()
767 {
768     this._lastCallbackId = 1;
769     this._pendingResponsesCount = 0;
770     this._callbacks = {};
771     this._domainDispatchers = {};
772     this._eventArgs = {};
773 $JSStubs
774 $JSEvents
775 $JSRegisterDomainDispatchers
776 }
777
778 InspectorBackendStub.prototype = {
779     dumpInspectorTimeStats: 0,
780     dumpInspectorProtocolMessages: 0,
781
782     _wrap: function(callback)
783     {
784         var callbackId = this._lastCallbackId++;
785         this._callbacks[callbackId] = callback || function() {};
786         return callbackId;
787     },
788
789     _registerDelegate: function(requestString)
790     {
791         var domainAndFunction = JSON.parse(requestString).method.split(".");
792         var agentName = domainAndFunction[0] + "Agent";
793         if (!window[agentName])
794             window[agentName] = {};
795         window[agentName][domainAndFunction[1]] = this._sendMessageToBackend.bind(this, requestString);
796         window[agentName][domainAndFunction[1]]["invoke"] = this._invoke.bind(this, requestString)
797     },
798
799     _invoke: function(requestString, args, callback)
800     {
801         var request = JSON.parse(requestString);
802         request.params = args;
803         this._wrapCallbackAndSendMessageObject(request, callback);
804     },
805
806     _sendMessageToBackend: function()
807     {
808         var args = Array.prototype.slice.call(arguments);
809         var request = JSON.parse(args.shift());
810         var callback = (args.length && typeof args[args.length - 1] === "function") ? args.pop() : 0;
811         var domainAndMethod = request.method.split(".");
812         var agentMethod = domainAndMethod[0] + "Agent." + domainAndMethod[1];
813
814         var hasParams = false;
815         if (request.params) {
816             for (var key in request.params) {
817                 var typeName = request.params[key].type;
818                 var optionalFlag = request.params[key].optional;
819
820                 if (args.length === 0 && !optionalFlag) {
821                     console.error("Protocol Error: Invalid number of arguments for method '" + agentMethod + "' call. It must have the next arguments '" + JSON.stringify(request.params) + "'.");
822                     return;
823                 }
824
825                 var value = args.shift();
826                 if (optionalFlag && typeof value === "undefined") {
827                     delete request.params[key];
828                     continue;
829                 }
830
831                 if (typeof value !== typeName) {
832                     console.error("Protocol Error: Invalid type of argument '" + key + "' for method '" + agentMethod + "' call. It must be '" + typeName + "' but it is '" + typeof value + "'.");
833                     return;
834                 }
835
836                 request.params[key] = value;
837                 hasParams = true;
838             }
839             if (!hasParams)
840                 delete request.params;
841         }
842
843         if (args.length === 1 && !callback) {
844             if (typeof args[0] !== "undefined") {
845                 console.error("Protocol Error: Optional callback argument for method '" + agentMethod + "' call must be a function but its type is '" + typeof args[0] + "'.");
846                 return;
847             }
848         }
849
850         this._wrapCallbackAndSendMessageObject(request, callback);
851     },
852
853     _wrapCallbackAndSendMessageObject: function(messageObject, callback)
854     {
855         messageObject.id = this._wrap(callback);
856
857         if (this.dumpInspectorTimeStats) {
858             var wrappedCallback = this._callbacks[messageObject.id];
859             wrappedCallback.methodName = messageObject.method;
860             wrappedCallback.sendRequestTime = Date.now();
861         }
862
863         if (this.dumpInspectorProtocolMessages)
864             console.log("frontend: " + JSON.stringify(messageObject));
865
866         ++this._pendingResponsesCount;
867         this.sendMessageObjectToBackend(messageObject);
868     },
869
870     sendMessageObjectToBackend: function(messageObject)
871     {
872         console.timeStamp(messageObject.method);
873         var message = JSON.stringify(messageObject);
874         InspectorFrontendHost.sendMessageToBackend(message);
875     },
876
877     _registerDomainDispatcher: function(domain, dispatcher)
878     {
879         this._domainDispatchers[domain] = dispatcher;
880     },
881
882     dispatch: function(message)
883     {
884         if (this.dumpInspectorProtocolMessages)
885             console.log("backend: " + ((typeof message === "string") ? message : JSON.stringify(message)));
886
887         var messageObject = (typeof message === "string") ? JSON.parse(message) : message;
888
889         if ("id" in messageObject) { // just a response for some request
890             if (messageObject.error) {
891                 messageObject.error.__proto__ = {
892                     getDescription: function()
893                     {
894                         switch(this.code) {
895                             case -32700: return "Parse error";
896                             case -32600: return "Invalid Request";
897                             case -32601: return "Method not found";
898                             case -32602: return "Invalid params";
899                             case -32603: return "Internal error";;
900                             case -32000: return "Server error";
901                         }
902                     },
903
904                     toString: function()
905                     {
906                         var description ="Unknown error code";
907                         return this.getDescription() + "(" + this.code + "): " + this.message + "." + (this.data ? " " + this.data.join(" ") : "");
908                     },
909
910                     getMessage: function()
911                     {
912                         return this.message;
913                     }
914                 }
915
916                 if (messageObject.error.code !== -32000)
917                     this.reportProtocolError(messageObject);
918             }
919
920             var arguments = [];
921             if (messageObject.result) {
922                 for (var key in messageObject.result)
923                     arguments.push(messageObject.result[key]);
924             }
925
926             var callback = this._callbacks[messageObject.id];
927             if (callback) {
928                 var processingStartTime;
929                 if (this.dumpInspectorTimeStats && callback.methodName)
930                     processingStartTime = Date.now();
931
932                 arguments.unshift(messageObject.error);
933                 callback.apply(null, arguments);
934                 --this._pendingResponsesCount;
935                 delete this._callbacks[messageObject.id];
936
937                 if (this.dumpInspectorTimeStats && callback.methodName)
938                     console.log("time-stats: " + callback.methodName + " = " + (processingStartTime - callback.sendRequestTime) + " + " + (Date.now() - processingStartTime));
939             }
940
941             if (this._scripts && !this._pendingResponsesCount)
942                 this.runAfterPendingDispatches();
943
944             return;
945         } else {
946             var method = messageObject.method.split(".");
947             var domainName = method[0];
948             var functionName = method[1];
949             if (!(domainName in this._domainDispatchers)) {
950                 console.error("Protocol Error: the message is for non-existing domain '" + domainName + "'");
951                 return;
952             }
953             var dispatcher = this._domainDispatchers[domainName];
954             if (!(functionName in dispatcher)) {
955                 console.error("Protocol Error: Attempted to dispatch an unimplemented method '" + messageObject.method + "'");
956                 return;
957             }
958
959             if (!this._eventArgs[messageObject.method]) {
960                 console.error("Protocol Error: Attempted to dispatch an unspecified method '" + messageObject.method + "'");
961                 return;
962             }
963
964             var params = [];
965             if (messageObject.params) {
966                 var paramNames = this._eventArgs[messageObject.method];
967                 for (var i = 0; i < paramNames.length; ++i)
968                     params.push(messageObject.params[paramNames[i]]);
969             }
970
971             var processingStartTime;
972             if (this.dumpInspectorTimeStats)
973                 processingStartTime = Date.now();
974
975             dispatcher[functionName].apply(dispatcher, params);
976
977             if (this.dumpInspectorTimeStats)
978                 console.log("time-stats: " + messageObject.method + " = " + (Date.now() - processingStartTime));
979         }
980     },
981
982     reportProtocolError: function(messageObject)
983     {
984         console.error("Request with id = " + messageObject.id + " failed. " + messageObject.error);
985     },
986
987     runAfterPendingDispatches: function(script)
988     {
989         if (!this._scripts)
990             this._scripts = [];
991
992         if (script)
993             this._scripts.push(script);
994
995         if (!this._pendingResponsesCount) {
996             var scripts = this._scripts;
997             this._scripts = []
998             for (var id = 0; id < scripts.length; ++id)
999                  scripts[id].call(this);
1000         }
1001     }
1002 }
1003
1004 InspectorBackend = new InspectorBackendStub();
1005
1006 EOF
1007     return split("\n", $inspectorBackendStubJS);
1008 }
1009
1010 sub generateHeader
1011 {
1012     my $className = shift;
1013     my $classDeclaration = shift;
1014     my $types = shift;
1015     my $constructor = shift;
1016     my $constants = shift;
1017     my $methods = shift;
1018     my $footer = shift;
1019
1020     my $forwardHeaders = join("\n", sort(map("#include <" . typeTraits($_, "forwardHeader") . ">", grep(typeTraits($_, "forwardHeader"), keys %{$types}))));
1021     my $forwardDeclarations = join("\n", sort(map("class " . typeTraits($_, "forward") . ";", grep(typeTraits($_, "forward"), keys %{$types}))));
1022     my $constantDeclarations = join("\n", @{$constants});
1023     my $methodsDeclarations = join("\n", @{$methods});
1024
1025     my $headerBody = << "EOF";
1026 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
1027 // Use of this source code is governed by a BSD-style license that can be
1028 // found in the LICENSE file.
1029 #ifndef ${className}_h
1030 #define ${className}_h
1031
1032 ${forwardHeaders}
1033
1034 namespace $namespace {
1035
1036 $forwardDeclarations
1037
1038 typedef String ErrorString;
1039
1040 class $classDeclaration {
1041 public:
1042 $constructor
1043
1044 $constantDeclarations
1045 $methodsDeclarations
1046
1047 $footer
1048 };
1049
1050 } // namespace $namespace
1051 #endif // !defined(${className}_h)
1052
1053 EOF
1054     return $headerBody;
1055 }
1056
1057 sub generateSource
1058 {
1059     my $className = shift;
1060     my $types = shift;
1061     my $constants = shift;
1062     my $methods = shift;
1063
1064     my @sourceContent = split("\r", $licenseTemplate);
1065     push(@sourceContent, "\n#include \"config.h\"");
1066     push(@sourceContent, "#include \"$className.h\"");
1067     push(@sourceContent, "#include <wtf/text/WTFString.h>");
1068     push(@sourceContent, "#include <wtf/text/CString.h>");
1069     push(@sourceContent, "");
1070     push(@sourceContent, "#if ENABLE(INSPECTOR)");
1071     push(@sourceContent, "");
1072
1073     my %headers;
1074     foreach my $type (keys %{$types}) {
1075         $headers{"#include \"" . typeTraits($type, "header") . "\""} = 1 if !typeTraits($type, "header") eq  "";
1076     }
1077     push(@sourceContent, sort keys %headers);
1078     push(@sourceContent, "");
1079     push(@sourceContent, "namespace $namespace {");
1080     push(@sourceContent, "");
1081     push(@sourceContent, join("\n", @{$constants}));
1082     push(@sourceContent, "");
1083     push(@sourceContent, @{$methods});
1084     push(@sourceContent, "");
1085     push(@sourceContent, "} // namespace $namespace");
1086     push(@sourceContent, "");
1087     push(@sourceContent, "#endif // ENABLE(INSPECTOR)");
1088     push(@sourceContent, "");
1089     return @sourceContent;
1090 }
1091
1092 sub typeTraits
1093 {
1094     my $type = shift;
1095     my $trait = shift;
1096     return $typeTransform{$type}->{$trait};
1097 }
1098
1099 sub generateBackendAgentFieldsAndConstructor
1100 {
1101     my @arguments;
1102     my @fieldInitializers;
1103
1104     push(@arguments, "InspectorFrontendChannel* inspectorFrontendChannel");
1105     push(@fieldInitializers, "        : m_inspectorFrontendChannel(inspectorFrontendChannel)");
1106     push(@backendFooter, "    InspectorFrontendChannel* m_inspectorFrontendChannel;");
1107
1108     foreach my $domain (sort keys %backendDomains) {
1109         # Add agent field declaration to the footer.
1110         my $agentClassName = typeTraits($domain, "forward");
1111         my $field = typeTraits($domain, "domainAccessor");
1112         push(@backendFooter, "    ${agentClassName}* ${field};");
1113
1114         # Add agent parameter and initializer.
1115         my $arg = substr($field, 2);
1116         push(@fieldInitializers, "        , ${field}(${arg})");
1117         push(@arguments, "${agentClassName}* ${arg}");
1118     }
1119
1120     my $argumentString = join(", ", @arguments);
1121
1122     my @backendHead;
1123     push(@backendHead, "    ${backendClassName}(${argumentString})");
1124     push(@backendHead, @fieldInitializers);
1125     push(@backendHead, "    { }");
1126     push(@backendHead, "");
1127     push(@backendHead, "    void clearFrontend() { m_inspectorFrontendChannel = 0; }");
1128     push(@backendHead, "");
1129     push(@backendHead, "    enum CommonErrorCode {");
1130     push(@backendHead, "        ParseError = 0,");
1131     push(@backendHead, "        InvalidRequest,");
1132     push(@backendHead, "        MethodNotFound,");
1133     push(@backendHead, "        InvalidParams,");
1134     push(@backendHead, "        InternalError,");
1135     push(@backendHead, "        ServerError,");
1136     push(@backendHead, "        LastEntry,");
1137     push(@backendHead, "    };");
1138     push(@backendHead, "");
1139     push(@backendHead, "    void reportProtocolError(const long* const callId, CommonErrorCode, const String& errorMessage) const;");
1140     push(@backendHead, "    void reportProtocolError(const long* const callId, CommonErrorCode, const String& errorMessage, PassRefPtr<InspectorArray> data) const;");
1141     push(@backendHead, "    void dispatch(const String& message);");
1142     push(@backendHead, "    static bool getCommandName(const String& message, String* result);");
1143     push(@backendHead, "");
1144     push(@backendHead, "    enum MethodNames {");
1145     $backendConstructor = join("\n", @backendHead);
1146 }
1147
1148 sub finish
1149 {
1150     my $object = shift;
1151
1152     push(@backendMethodsImpl, generateBackendDispatcher());
1153     push(@backendMethodsImpl, generateBackendSendResponse());
1154     push(@backendMethodsImpl, generateBackendReportProtocolError());
1155     unshift(@frontendMethodsImpl, generateFrontendConstructorImpl(), "");
1156
1157     open(my $SOURCE, ">$outputDir/$frontendClassName.cpp") || die "Couldn't open file $outputDir/$frontendClassName.cpp";
1158     print $SOURCE join("\n", generateSource($frontendClassName, \%frontendTypes, \@frontendConstantDefinitions, \@frontendMethodsImpl));
1159     close($SOURCE);
1160     undef($SOURCE);
1161
1162     open(my $HEADER, ">$outputHeadersDir/$frontendClassName.h") || die "Couldn't open file $outputHeadersDir/$frontendClassName.h";
1163     print $HEADER generateHeader($frontendClassName, $frontendClassName, \%frontendTypes, $frontendConstructor, \@frontendConstantDeclarations, \@frontendMethods, join("\n", @frontendFooter));
1164     close($HEADER);
1165     undef($HEADER);
1166
1167     unshift(@backendConstantDefinitions, "const char* InspectorBackendDispatcher::commandNames[] = {");
1168     push(@backendConstantDefinitions, "};");
1169     push(@backendConstantDefinitions, "");
1170
1171     # Make dispatcher methods private on the backend.
1172     push(@backendConstantDeclarations, "};");
1173     push(@backendConstantDeclarations, "");
1174     push(@backendConstantDeclarations, "    static const char* commandNames[];");    
1175     push(@backendConstantDeclarations, "");
1176     push(@backendConstantDeclarations, "private:");
1177
1178     foreach my $type (keys %backendTypes) {
1179         if (typeTraits($type, "JSONType")) {
1180             push(@backendMethodsImpl, generateArgumentGetters($type));
1181         }
1182     }
1183
1184     push(@backendConstantDeclarations, "    void sendResponse(long callId, PassRefPtr<InspectorObject> result, const String& errorMessage, PassRefPtr<InspectorArray> protocolErrors, ErrorString invocationError);");
1185
1186     generateBackendAgentFieldsAndConstructor();
1187
1188     push(@backendMethodsImpl, generateBackendMessageParser());
1189     push(@backendMethodsImpl, "");
1190
1191     push(@backendConstantDeclarations, "");
1192
1193     open($SOURCE, ">$outputDir/$backendClassName.cpp") || die "Couldn't open file $outputDir/$backendClassName.cpp";
1194     print $SOURCE join("\n", generateSource($backendClassName, \%backendTypes, \@backendConstantDefinitions, \@backendMethodsImpl));
1195     close($SOURCE);
1196     undef($SOURCE);
1197
1198     open($HEADER, ">$outputHeadersDir/$backendClassName.h") || die "Couldn't open file $outputHeadersDir/$backendClassName.h";
1199     print $HEADER join("\n", generateHeader($backendClassName, $backendClassDeclaration, \%backendTypes, $backendConstructor, \@backendConstantDeclarations, \@backendMethods, join("\n", @backendFooter)));
1200     close($HEADER);
1201     undef($HEADER);
1202
1203     open(my $JS_STUB, ">$outputDir/$backendJSStubName.js") || die "Couldn't open file $outputDir/$backendJSStubName.js";
1204     print $JS_STUB join("\n", generateBackendStubJS());
1205     close($JS_STUB);
1206     undef($JS_STUB);
1207 }
1208
1209 1;