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