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