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