All prototypes should call didBecomePrototype()
[WebKit.git] / Source / WebCore / bindings / scripts / preprocess-idls.pl
1 #!/usr/bin/env perl
2 #
3 # Copyright (C) 2011 Google Inc.  All rights reserved.
4 #
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Library General Public
7 # License as published by the Free Software Foundation; either
8 # version 2 of the License, or (at your option) any later version.
9 #
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # Library General Public License for more details.
14 #
15 # You should have received a copy of the GNU Library General Public License
16 # along with this library; see the file COPYING.LIB.  If not, write to
17 # the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 # Boston, MA 02110-1301, USA.
19 #
20
21 use strict;
22 use warnings;
23 use FindBin;
24 use lib $FindBin::Bin;
25
26 use File::Basename;
27 use Getopt::Long;
28 use Cwd;
29 use Config;
30
31 my $defines;
32 my $preprocessor;
33 my $idlFilesList;
34 my $testGlobalContextName;
35 my $supplementalDependencyFile;
36 my $windowConstructorsFile;
37 my $workerGlobalScopeConstructorsFile;
38 my $dedicatedWorkerGlobalScopeConstructorsFile;
39 my $serviceWorkerGlobalScopeConstructorsFile;
40 my $workletGlobalScopeConstructorsFile;
41 my $paintWorkletGlobalScopeConstructorsFile;
42 my $testGlobalScopeConstructorsFile;
43 my $supplementalMakefileDeps;
44
45 GetOptions('defines=s' => \$defines,
46            'preprocessor=s' => \$preprocessor,
47            'idlFilesList=s' => \$idlFilesList,
48            'testGlobalContextName=s' => \$testGlobalContextName,
49            'supplementalDependencyFile=s' => \$supplementalDependencyFile,
50            'windowConstructorsFile=s' => \$windowConstructorsFile,
51            'workerGlobalScopeConstructorsFile=s' => \$workerGlobalScopeConstructorsFile,
52            'dedicatedWorkerGlobalScopeConstructorsFile=s' => \$dedicatedWorkerGlobalScopeConstructorsFile,
53            'serviceWorkerGlobalScopeConstructorsFile=s' => \$serviceWorkerGlobalScopeConstructorsFile,
54            'workletGlobalScopeConstructorsFile=s' => \$workletGlobalScopeConstructorsFile,
55            'paintWorkletGlobalScopeConstructorsFile=s' => \$paintWorkletGlobalScopeConstructorsFile,
56            'testGlobalScopeConstructorsFile=s' => \$testGlobalScopeConstructorsFile,
57            'supplementalMakefileDeps=s' => \$supplementalMakefileDeps);
58
59 die('Must specify #define macros using --defines.') unless defined($defines);
60 die('Must specify an output file using --supplementalDependencyFile.') unless defined($supplementalDependencyFile);
61 die('Must specify an output file using --windowConstructorsFile.') unless defined($windowConstructorsFile);
62 die('Must specify an output file using --workerGlobalScopeConstructorsFile.') unless defined($workerGlobalScopeConstructorsFile);
63 die('Must specify an output file using --dedicatedWorkerGlobalScopeConstructorsFile.') unless defined($dedicatedWorkerGlobalScopeConstructorsFile);
64 die('Must specify an output file using --serviceWorkerGlobalScopeConstructorsFile.') unless defined($serviceWorkerGlobalScopeConstructorsFile);
65 die('Must specify an output file using --workletGlobalScopeConstructorsFile.') unless defined($workletGlobalScopeConstructorsFile);
66 die('Must specify an output file using --paintWorkletGlobalScopeConstructorsFile.') unless defined($paintWorkletGlobalScopeConstructorsFile);
67 die('Must specify an output file using --testGlobalScopeConstructorsFile.') unless defined($testGlobalScopeConstructorsFile) || !defined($testGlobalContextName);
68 die('Must specify the file listing all IDLs using --idlFilesList.') unless defined($idlFilesList);
69
70 $supplementalDependencyFile = CygwinPathIfNeeded($supplementalDependencyFile);
71 $windowConstructorsFile = CygwinPathIfNeeded($windowConstructorsFile);
72 $workerGlobalScopeConstructorsFile = CygwinPathIfNeeded($workerGlobalScopeConstructorsFile);
73 $dedicatedWorkerGlobalScopeConstructorsFile = CygwinPathIfNeeded($dedicatedWorkerGlobalScopeConstructorsFile);
74 $serviceWorkerGlobalScopeConstructorsFile = CygwinPathIfNeeded($serviceWorkerGlobalScopeConstructorsFile);
75 $workletGlobalScopeConstructorsFile = CygwinPathIfNeeded($workletGlobalScopeConstructorsFile);
76 $paintWorkletGlobalScopeConstructorsFile = CygwinPathIfNeeded($paintWorkletGlobalScopeConstructorsFile);
77 $supplementalMakefileDeps = CygwinPathIfNeeded($supplementalMakefileDeps);
78
79 open FH, "< $idlFilesList" or die "Cannot open $idlFilesList\n";
80 my @idlFilesIn = <FH>;
81 chomp(@idlFilesIn);
82 my @idlFiles = ();
83 foreach (@idlFilesIn) {
84     push @idlFiles, CygwinPathIfNeeded($_);
85 }
86 close FH;
87
88 my %interfaceNameToIdlFile;
89 my %idlFileToInterfaceName;
90 my %supplementalDependencies;
91 my %supplementals;
92 my $windowConstructorsCode = "";
93 my $workerGlobalScopeConstructorsCode = "";
94 my $dedicatedWorkerGlobalScopeConstructorsCode = "";
95 my $serviceWorkerGlobalScopeConstructorsCode = "";
96 my $workletGlobalScopeConstructorsCode = "";
97 my $paintWorkletGlobalScopeConstructorsCode = "";
98 my $testGlobalScopeConstructorsCode = "";
99
100 # Get rid of duplicates in idlFiles array.
101 my %idlFileHash = map { $_, 1 } @idlFiles;
102
103 # Populate $idlFileToInterfaceName and $interfaceNameToIdlFile.
104 foreach my $idlFile (sort keys %idlFileHash) {
105     my $fullPath = Cwd::realpath($idlFile);
106     my $interfaceName = fileparse(basename($idlFile), ".idl");
107     $idlFileToInterfaceName{$fullPath} = $interfaceName;
108     $interfaceNameToIdlFile{$interfaceName} = $fullPath;
109 }
110
111 # Parse all IDL files.
112 foreach my $idlFile (sort keys %idlFileHash) {
113     my $fullPath = Cwd::realpath($idlFile);
114     my $idlFileContents = getFileContents($fullPath);
115     # Handle partial interfaces.
116     my $partialInterfaceName = getPartialInterfaceNameFromIDL($idlFileContents);
117     if ($partialInterfaceName) {
118         $supplementalDependencies{$fullPath} = [$partialInterfaceName];
119         next;
120     }
121
122     $supplementals{$fullPath} = [];
123
124     # Skip if the IDL file does not contain an interface, a callback interface or an exception.
125     # The IDL may contain a dictionary.
126     next unless containsInterfaceOrExceptionFromIDL($idlFileContents);
127
128     my $interfaceName = fileparse(basename($idlFile), ".idl");
129     # Handle implements statements.
130     my $implementedInterfaces = getImplementedInterfacesFromIDL($idlFileContents, $interfaceName);
131     foreach my $implementedInterface (@{$implementedInterfaces}) {
132         my $implementedIdlFile = $interfaceNameToIdlFile{$implementedInterface};
133         die "Could not find a the IDL file where the following implemented interface is defined: $implementedInterface" unless $implementedIdlFile;
134         if ($supplementalDependencies{$implementedIdlFile}) {
135             push(@{$supplementalDependencies{$implementedIdlFile}}, $interfaceName);
136         } else {
137             $supplementalDependencies{$implementedIdlFile} = [$interfaceName];
138         }
139     }
140
141     # For every interface that is exposed in a given ECMAScript global environment and:
142     # - is a callback interface that has constants declared on it, or
143     # - is a non-callback interface that is not declared with the [NoInterfaceObject] extended attribute, a corresponding
144     #   property must exist on the ECMAScript environment's global object.
145     # See https://heycam.github.io/webidl/#es-interfaces
146     my $extendedAttributes = getInterfaceExtendedAttributesFromIDL($idlFileContents);
147     if (shouldExposeInterface($extendedAttributes)) {
148         if (!isCallbackInterfaceFromIDL($idlFileContents) || interfaceHasConstantAttribute($idlFileContents)) {
149             my $exposedAttribute = $extendedAttributes->{"Exposed"} || $testGlobalContextName || "Window";
150             $exposedAttribute = substr($exposedAttribute, 1, -1) if substr($exposedAttribute, 0, 1) eq "(";
151             my @globalContexts = split(",", $exposedAttribute);
152             my ($attributeCode, $windowAliases) = GenerateConstructorAttributes($interfaceName, $extendedAttributes);
153             foreach my $globalContext (@globalContexts) {
154                 if ($globalContext eq "Window") {
155                     $windowConstructorsCode .= $attributeCode;
156                 } elsif ($globalContext eq "Worker") {
157                     $workerGlobalScopeConstructorsCode .= $attributeCode;
158                 } elsif ($globalContext eq "DedicatedWorker") {
159                     $dedicatedWorkerGlobalScopeConstructorsCode .= $attributeCode;
160                 } elsif ($globalContext eq "ServiceWorker") {
161                     $serviceWorkerGlobalScopeConstructorsCode .= $attributeCode;
162                 } elsif ($globalContext eq "Worklet") {
163                     $workletGlobalScopeConstructorsCode .= $attributeCode;
164                 } elsif ($globalContext eq "PaintWorklet") {
165                     $paintWorkletGlobalScopeConstructorsCode .= $attributeCode;
166                 } elsif ($globalContext eq $testGlobalContextName) {
167                     $testGlobalScopeConstructorsCode .= $attributeCode;
168                 } else {
169                     die "Unsupported global context '$globalContext' used in [Exposed] at $idlFile";
170                 }
171             }
172             $windowConstructorsCode .= $windowAliases if $windowAliases;
173         }
174     }
175 }
176
177 # Generate partial interfaces for Constructors.
178 GeneratePartialInterface("DOMWindow", $windowConstructorsCode, $windowConstructorsFile);
179 GeneratePartialInterface("WorkerGlobalScope", $workerGlobalScopeConstructorsCode, $workerGlobalScopeConstructorsFile);
180 GeneratePartialInterface("DedicatedWorkerGlobalScope", $dedicatedWorkerGlobalScopeConstructorsCode, $dedicatedWorkerGlobalScopeConstructorsFile);
181 GeneratePartialInterface("ServiceWorkerGlobalScope", $serviceWorkerGlobalScopeConstructorsCode, $serviceWorkerGlobalScopeConstructorsFile);
182 GeneratePartialInterface("WorkletGlobalScope", $workletGlobalScopeConstructorsCode, $workletGlobalScopeConstructorsFile);
183 GeneratePartialInterface("PaintWorkletGlobalScope", $paintWorkletGlobalScopeConstructorsCode, $paintWorkletGlobalScopeConstructorsFile);
184 GeneratePartialInterface($testGlobalContextName, $testGlobalScopeConstructorsCode, $testGlobalScopeConstructorsFile) if defined($testGlobalContextName);
185
186 # Resolves partial interfaces and implements dependencies.
187 foreach my $idlFile (sort keys %supplementalDependencies) {
188     my $baseFiles = $supplementalDependencies{$idlFile};
189     foreach my $baseFile (@{$baseFiles}) {
190         my $targetIdlFile = $interfaceNameToIdlFile{$baseFile} or die "${baseFile}.idl not found, but it is supplemented by $idlFile";
191         push(@{$supplementals{$targetIdlFile}}, $idlFile);
192     }
193     delete $supplementals{$idlFile};
194 }
195
196 # Outputs the dependency.
197 # The format of a supplemental dependency file:
198 #
199 # DOMWindow.idl P.idl Q.idl R.idl
200 # Document.idl S.idl
201 # Event.idl
202 # ...
203 #
204 # The above indicates that DOMWindow.idl is supplemented by P.idl, Q.idl and R.idl,
205 # Document.idl is supplemented by S.idl, and Event.idl is supplemented by no IDLs.
206 # The IDL that supplements another IDL (e.g. P.idl) never appears in the dependency file.
207 my $dependencies = "";
208 foreach my $idlFile (sort keys %supplementals) {
209     $dependencies .= "$idlFile @{$supplementals{$idlFile}}\n";
210 }
211 WriteFileIfChanged($supplementalDependencyFile, $dependencies);
212
213 if ($supplementalMakefileDeps) {
214     my $makefileDeps = "";
215     foreach my $idlFile (sort keys %supplementals) {
216         my $basename = $idlFileToInterfaceName{$idlFile};
217
218         my @dependencies = map { basename($_) } @{$supplementals{$idlFile}};
219
220         $makefileDeps .= "JS${basename}.h: @{dependencies}\n";
221         $makefileDeps .= "DOM${basename}.h: @{dependencies}\n";
222         $makefileDeps .= "WebDOM${basename}.h: @{dependencies}\n";
223         foreach my $dependency (@dependencies) {
224             $makefileDeps .= "${dependency}:\n";
225         }
226     }
227
228     WriteFileIfChanged($supplementalMakefileDeps, $makefileDeps);
229 }
230
231 my $cygwinPathAdded;
232 sub CygwinPathIfNeeded
233 {
234     my $path = shift;
235     if ($path && $Config{osname} eq "cygwin") {
236         if (not $cygwinPathAdded) {
237             $ENV{PATH} = "$ENV{PATH}:/cygdrive/c/cygwin/bin";
238             $cygwinPathAdded = 1; 
239         }
240         chomp($path = `cygpath -u '$path'`);
241         $path =~ s/[\r\n]//;
242     }
243     return $path;
244 }
245
246 sub WriteFileIfChanged
247 {
248     my $fileName = shift;
249     my $contents = shift;
250
251     if (-f $fileName) {
252         open FH, "<", $fileName or die "Couldn't open $fileName: $!\n";
253         my @lines = <FH>;
254         my $oldContents = join "", @lines;
255         close FH;
256         return if $contents eq $oldContents;
257     }
258     open FH, ">", $fileName or die "Couldn't open $fileName: $!\n";
259     print FH $contents;
260     close FH;
261 }
262
263 sub GeneratePartialInterface
264 {
265     my $interfaceName = shift;
266     my $attributesCode = shift;
267     my $destinationFile = shift;
268
269     my $contents = "partial interface ${interfaceName} {\n$attributesCode};\n";
270     WriteFileIfChanged($destinationFile, $contents);
271
272     my $fullPath = Cwd::realpath($destinationFile);
273     $supplementalDependencies{$fullPath} = [$interfaceName] if $interfaceNameToIdlFile{$interfaceName};
274 }
275
276 sub GenerateConstructorAttributes
277 {
278     my $interfaceName = shift;
279     my $extendedAttributes = shift;
280
281     my $code = "    ";
282     my @extendedAttributesList;
283     foreach my $attributeName (sort keys %{$extendedAttributes}) {
284       next unless ($attributeName eq "Conditional" || $attributeName eq "EnabledAtRuntime" || $attributeName eq "EnabledForWorld"
285         || $attributeName eq "EnabledBySetting" || $attributeName eq "SecureContext" || $attributeName eq "PrivateIdentifier"
286         || $attributeName eq "PublicIdentifier" || $attributeName eq "DisabledByQuirk" || $attributeName eq "EnabledByQuirk");
287       my $extendedAttribute = $attributeName;
288       $extendedAttribute .= "=" . $extendedAttributes->{$attributeName} unless $extendedAttributes->{$attributeName} eq "VALUE_IS_MISSING";
289       push(@extendedAttributesList, $extendedAttribute);
290     }
291     $code .= "[" . join(', ', @extendedAttributesList) . "] " if @extendedAttributesList;
292
293     my $originalInterfaceName = $interfaceName;
294     $interfaceName = $extendedAttributes->{"InterfaceName"} if $extendedAttributes->{"InterfaceName"};
295     $code .= "attribute " . $originalInterfaceName . "Constructor $interfaceName;\n";
296
297     # In addition to the regular property, for every [NamedConstructor] extended attribute on an interface,
298     # a corresponding property MUST exist on the ECMAScript global object.
299     if ($extendedAttributes->{"NamedConstructor"}) {
300         my $constructorName = $extendedAttributes->{"NamedConstructor"};
301         $constructorName =~ s/\(.*//g; # Extract function name.
302         $code .= "    ";
303         $code .= "[" . join(', ', @extendedAttributesList) . "] " if @extendedAttributesList;
304         $code .= "attribute " . $originalInterfaceName . "NamedConstructor $constructorName;\n";
305     }
306     
307     my $windowAliasesCode;
308     if ($extendedAttributes->{"LegacyWindowAlias"}) {
309         my $attributeValue = $extendedAttributes->{"LegacyWindowAlias"};
310         $attributeValue = substr($attributeValue, 1, -1) if substr($attributeValue, 0, 1) eq "(";
311         my @windowAliases = split(",", $attributeValue);
312         foreach my $windowAlias (@windowAliases) {
313             $windowAliasesCode .= "    ";
314             $windowAliasesCode .= "[" . join(', ', @extendedAttributesList) . "] " if @extendedAttributesList;
315             $windowAliasesCode .= "attribute " . $originalInterfaceName . "Constructor $windowAlias; // Legacy Window alias.\n";
316         }
317     }
318     
319     return ($code, $windowAliasesCode);
320 }
321
322 sub getFileContents
323 {
324     my $idlFile = shift;
325
326     open FILE, "<", $idlFile;
327     my @lines = <FILE>;
328     close FILE;
329
330     # Filter out preprocessor lines.
331     @lines = grep(!/^\s*#/, @lines);
332
333     return join('', @lines);
334 }
335
336 sub getPartialInterfaceNameFromIDL
337 {
338     my $fileContents = shift;
339
340     if ($fileContents =~ /partial\s+interface\s+(\w+)/gs) {
341         return $1;
342     }
343 }
344
345 # identifier-A implements identifier-B;
346 # http://www.w3.org/TR/WebIDL/#idl-implements-statements
347 sub getImplementedInterfacesFromIDL
348 {
349     my $fileContents = shift;
350     my $interfaceName = shift;
351
352     my @implementedInterfaces = ();
353     while ($fileContents =~ /^\s*(\w+)\s+implements\s+(\w+)\s*;/mg) {
354         die "Identifier on the left of the 'implements' statement should be $interfaceName in $interfaceName.idl, but found $1" if $1 ne $interfaceName;
355         push(@implementedInterfaces, $2);
356     }
357     return \@implementedInterfaces
358 }
359
360 sub isCallbackInterfaceFromIDL
361 {
362     my $fileContents = shift;
363     return ($fileContents =~ /callback\s+interface\s+\w+/gs);
364 }
365
366 sub containsInterfaceOrExceptionFromIDL
367 {
368     my $fileContents = shift;
369
370     return 1 if $fileContents =~ /\bcallback\s+interface\s+\w+/gs;
371     return 1 if $fileContents =~ /\binterface\s+\w+/gs;
372     return 1 if $fileContents =~ /\bexception\s+\w+/gs;
373     return 0;
374 }
375
376 sub trim
377 {
378     my $string = shift;
379     $string =~ s/^\s+|\s+$//g;
380     return $string;
381 }
382
383 sub getInterfaceExtendedAttributesFromIDL
384 {
385     my $fileContents = shift;
386
387     my $extendedAttributes = {};
388
389     # Remove comments from fileContents before processing.
390     # FIX: Preference to use Regex::Common::comment, however it is not available on
391     # all build systems.
392     $fileContents =~ s/(?:(?:(?:\/\/)(?:[^\n]*)(?:\n))|(?:(?:\/\*)(?:(?:[^\*]+|\*(?!\/))*)(?:\*\/)))//g;
393
394     if ($fileContents =~ /\[(.*)\]\s+(callback interface|interface|exception)\s+(\w+)/gs) {
395         my @parts = split(m/,(?![^()]*\))/, $1);
396         foreach my $part (@parts) {
397             my @keyValue = split('=', $part);
398             my $key = trim($keyValue[0]);
399             next unless length($key);
400             my $value = "VALUE_IS_MISSING";
401             $value = trim($keyValue[1]) if @keyValue > 1;
402             $extendedAttributes->{$key} = $value;
403         }
404     }
405
406     return $extendedAttributes;
407 }
408
409 sub interfaceHasConstantAttribute
410 {
411     my $fileContents = shift;
412
413     return $fileContents =~ /\s+const[\s\w]+=\s+[\w]+;/gs;
414 }
415
416 sub shouldExposeInterface
417 {
418     my $extendedAttributes = shift;
419
420     return !$extendedAttributes->{"NoInterfaceObject"};
421 }