blob: e2f1e353f9b6b19095b267d7e6d082f37918dae3 [file] [log] [blame]
#!/usr/bin/env perl
#
# Copyright (C) 2011 Google Inc. All rights reserved.
# Copyright (C) 2020 Apple Inc. All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this library; see the file COPYING.LIB. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
#
use strict;
use warnings;
use FindBin;
use lib $FindBin::Bin;
use English;
use File::Basename;
use Getopt::Long;
use Cwd;
use Config;
use Class::Struct;
use JSON::PP;
use Data::Dumper;
use IDLParser;
my $defines;
my $preprocessor;
my $idlFileNamesList;
my $testGlobalContextName;
my $supplementalDependencyFile;
my $isoSubspacesHeaderFile;
my $constructorsHeaderFile;
my $windowConstructorsFile;
my $workerGlobalScopeConstructorsFile;
my $shadowRealmGlobalScopeConstructorsFile;
my $dedicatedWorkerGlobalScopeConstructorsFile;
my $serviceWorkerGlobalScopeConstructorsFile;
my $sharedWorkerGlobalScopeConstructorsFile;
my $workletGlobalScopeConstructorsFile;
my $paintWorkletGlobalScopeConstructorsFile;
my $audioWorkletGlobalScopeConstructorsFile;
my $testGlobalScopeConstructorsFile;
my $supplementalMakefileDeps;
my $idlAttributesFile;
my $verbose = 0;
# List of contexts with the [Global] extended attribute.
#
# Must not contain partial interfaces used by other interfaces in the list,
# in order to prevent the same attributes/interfaces being installed multiple
# times when [Exposed=*] is used.
my @supportedGlobalContexts = (
"Window",
"DedicatedWorker",
"ServiceWorker",
"PaintWorklet",
"AudioWorklet",
"ShadowRealm",
);
# Toggle this to validate that the fast regular expression based "parsing" used
# in this file produces the same results as the slower results produced by the
# complete IDLParser.
my $validateAgainstParser = 0;
GetOptions('defines=s' => \$defines,
'preprocessor=s' => \$preprocessor,
'idlFileNamesList=s' => \$idlFileNamesList,
'testGlobalContextName=s' => \$testGlobalContextName,
'supplementalDependencyFile=s' => \$supplementalDependencyFile,
'isoSubspacesHeaderFile=s' => \$isoSubspacesHeaderFile,
'constructorsHeaderFile=s' => \$constructorsHeaderFile,
'windowConstructorsFile=s' => \$windowConstructorsFile,
'workerGlobalScopeConstructorsFile=s' => \$workerGlobalScopeConstructorsFile,
'shadowRealmGlobalScopeConstructorsFile=s' => \$shadowRealmGlobalScopeConstructorsFile,
'dedicatedWorkerGlobalScopeConstructorsFile=s' => \$dedicatedWorkerGlobalScopeConstructorsFile,
'serviceWorkerGlobalScopeConstructorsFile=s' => \$serviceWorkerGlobalScopeConstructorsFile,
'sharedWorkerGlobalScopeConstructorsFile=s' => \$sharedWorkerGlobalScopeConstructorsFile,
'workletGlobalScopeConstructorsFile=s' => \$workletGlobalScopeConstructorsFile,
'paintWorkletGlobalScopeConstructorsFile=s' => \$paintWorkletGlobalScopeConstructorsFile,
'audioWorkletGlobalScopeConstructorsFile=s' => \$audioWorkletGlobalScopeConstructorsFile,
'testGlobalScopeConstructorsFile=s' => \$testGlobalScopeConstructorsFile,
'supplementalMakefileDeps=s' => \$supplementalMakefileDeps,
'idlAttributesFile=s' => \$idlAttributesFile,
'validateAgainstParser' => \$validateAgainstParser,
'verbose' => \$verbose);
die('Must specify #define macros using --defines.') unless defined($defines);
die('Must specify an output file using --supplementalDependencyFile.') unless defined($supplementalDependencyFile);
die('Must specify an output file using --windowConstructorsFile.') unless defined($windowConstructorsFile);
die('Must specify an output file using --workerGlobalScopeConstructorsFile.') unless defined($workerGlobalScopeConstructorsFile);
die('Must specify an output file using --shadowRealmGlobalScopeConstructorsFile.') unless defined($shadowRealmGlobalScopeConstructorsFile);
die('Must specify an output file using --dedicatedWorkerGlobalScopeConstructorsFile.') unless defined($dedicatedWorkerGlobalScopeConstructorsFile);
die('Must specify an output file using --serviceWorkerGlobalScopeConstructorsFile.') unless defined($serviceWorkerGlobalScopeConstructorsFile);
die('Must specify an output file using --sharedWorkerGlobalScopeConstructorsFile.') unless defined($sharedWorkerGlobalScopeConstructorsFile);
die('Must specify an output file using --workletGlobalScopeConstructorsFile.') unless defined($workletGlobalScopeConstructorsFile);
die('Must specify an output file using --paintWorkletGlobalScopeConstructorsFile.') unless defined($paintWorkletGlobalScopeConstructorsFile);
die('Must specify an output file using --audioWorkletGlobalScopeConstructorsFile.') unless defined($audioWorkletGlobalScopeConstructorsFile);
die('Must specify an output file using --testGlobalScopeConstructorsFile.') unless defined($testGlobalScopeConstructorsFile) || !defined($testGlobalContextName);
die('Must specify the file listing all IDLs using --idlFileNamesList.') unless defined($idlFileNamesList);
die('Must specify IDL attributes file using --idlAttributesFile.') unless defined($idlAttributesFile);
$supplementalDependencyFile = CygwinPathIfNeeded($supplementalDependencyFile);
$isoSubspacesHeaderFile = CygwinPathIfNeeded($isoSubspacesHeaderFile);
$constructorsHeaderFile = CygwinPathIfNeeded($constructorsHeaderFile);
$windowConstructorsFile = CygwinPathIfNeeded($windowConstructorsFile);
$workerGlobalScopeConstructorsFile = CygwinPathIfNeeded($workerGlobalScopeConstructorsFile);
$shadowRealmGlobalScopeConstructorsFile = CygwinPathIfNeeded($shadowRealmGlobalScopeConstructorsFile);
$dedicatedWorkerGlobalScopeConstructorsFile = CygwinPathIfNeeded($dedicatedWorkerGlobalScopeConstructorsFile);
$serviceWorkerGlobalScopeConstructorsFile = CygwinPathIfNeeded($serviceWorkerGlobalScopeConstructorsFile);
$workletGlobalScopeConstructorsFile = CygwinPathIfNeeded($workletGlobalScopeConstructorsFile);
$paintWorkletGlobalScopeConstructorsFile = CygwinPathIfNeeded($paintWorkletGlobalScopeConstructorsFile);
$audioWorkletGlobalScopeConstructorsFile = CygwinPathIfNeeded($audioWorkletGlobalScopeConstructorsFile);
$supplementalMakefileDeps = CygwinPathIfNeeded($supplementalMakefileDeps) if defined($supplementalMakefileDeps);
my @idlFileNames;
open(my $fh, '<', $idlFileNamesList) or die "Cannot open $idlFileNamesList";
@idlFileNames = map { CygwinPathIfNeeded(s/\r?\n?$//r) } <$fh>;
close($fh) or die;
my $idlAttributes;
if ($validateAgainstParser) {
my $input;
{
local $INPUT_RECORD_SEPARATOR;
open(JSON, "<", $idlAttributesFile) or die "Couldn't open $idlAttributesFile: $!";
$input = <JSON>;
close(JSON);
}
my $jsonDecoder = JSON::PP->new->utf8;
my $jsonHashRef = $jsonDecoder->decode($input);
$idlAttributes = $jsonHashRef->{attributes};
}
struct( IDLFile => {
fileName => '$',
fileContents => '$',
primaryDeclarationName => '$',
parsedDocument => '$'
});
my %interfaceNameToIdlFilePath;
my %idlFilePathToInterfaceName;
my %supplementalDependencies;
my %dictionaryDependencies;
my %supplementals;
my $windowConstructorsCode = "";
my $workerGlobalScopeConstructorsCode = "";
my $shadowRealmGlobalScopeConstructorsCode = "";
my $dedicatedWorkerGlobalScopeConstructorsCode = "";
my $serviceWorkerGlobalScopeConstructorsCode = "";
my $sharedWorkerGlobalScopeConstructorsCode = "";
my $workletGlobalScopeConstructorsCode = "";
my $paintWorkletGlobalScopeConstructorsCode = "";
my $audioWorkletGlobalScopeConstructorsCode = "";
my $testGlobalScopeConstructorsCode = "";
my $isoSubspacesHeaderCode = <<END;
#include <wtf/FastMalloc.h>
#include <wtf/Noncopyable.h>
#pragma once
namespace WebCore {
class DOMIsoSubspaces {
WTF_MAKE_NONCOPYABLE(DOMIsoSubspaces);
WTF_MAKE_FAST_ALLOCATED(DOMIsoSubspaces);
public:
DOMIsoSubspaces() = default;
END
my @constructors = ();
my $constructorsHeaderCode = <<END;
#include <wtf/FastMalloc.h>
#include <wtf/Noncopyable.h>
#include <JavaScriptCore/JSCInlines.h>
#pragma once
namespace WebCore {
enum class DOMConstructorID : uint16_t {
END
# Get rid of duplicates in idlFileNames array.
my %idlFileNameHash = map { $_, 1 } @idlFileNames;
# Populate $idlFilePathToInterfaceName and $interfaceNameToIdlFilePath.
foreach my $idlFileName (sort keys %idlFileNameHash) {
my $fullPath = Cwd::realpath($idlFileName);
my $interfaceName = fileparse(basename($idlFileName), ".idl");
$idlFilePathToInterfaceName{$fullPath} = $interfaceName;
$interfaceNameToIdlFilePath{$interfaceName} = $fullPath;
}
# Parse all IDL files.
foreach my $idlFileName (sort keys %idlFileNameHash) {
my $fullPath = Cwd::realpath($idlFileName);
my $idlFile = processIDL($idlFileName, $fullPath);
# Handle partial names.
my $partialNames = getPartialNamesFromIDL($idlFile);
if (@{$partialNames}) {
$supplementalDependencies{$fullPath} = $partialNames;
next;
}
$supplementals{$fullPath} = [];
my $baseDictionaries = getUndefinedBaseDictionariesFromIDL($idlFile);
if (@{$baseDictionaries}) {
$dictionaryDependencies{$idlFile->primaryDeclarationName} = (join(".idl ", @{$baseDictionaries}) . ".idl");
}
# Skip if the IDL file does not contain an interface or a callback interface.
# The IDL may contain a dictionary.
next unless containsInterfaceOrCallbackInterfaceFromIDL($idlFile);
my $interfaceName = $idlFile->primaryDeclarationName;
# Handle include statements.
my $includedInterfaces = getIncludedInterfacesFromIDL($idlFile, $interfaceName);
foreach my $includedInterface (@{$includedInterfaces}) {
my $includedIdlFilePath = $interfaceNameToIdlFilePath{$includedInterface};
die "Could not find a the IDL file where the following included interface is defined: $includedInterface" unless $includedIdlFilePath;
if ($supplementalDependencies{$includedIdlFilePath}) {
push(@{$supplementalDependencies{$includedIdlFilePath}}, $interfaceName);
} else {
$supplementalDependencies{$includedIdlFilePath} = [$interfaceName];
}
}
next if isMixinInterfaceFromIDL($idlFile);
my $isCallbackInterface = isCallbackInterfaceFromIDL($idlFile);
if (!$isCallbackInterface) {
$isoSubspacesHeaderCode .= " std::unique_ptr<JSC::IsoSubspace> m_subspaceFor${interfaceName};\n";
if (containsIterableInterfaceFromIDL($idlFile)) {
$isoSubspacesHeaderCode .= " std::unique_ptr<JSC::IsoSubspace> m_subspaceFor${interfaceName}Iterator;\n";
}
}
# For every interface that is exposed in a given ECMAScript global environment and:
# - is a callback interface that has constants declared on it, or
# - is a non-callback interface that is not declared with the [LegacyNoInterfaceObject] extended attribute, a corresponding
# property must exist on the ECMAScript environment's global object.
# See https://webidl.spec.whatwg.org/#es-interfaces
my $extendedAttributes = getInterfaceExtendedAttributesFromIDL($idlFile);
if (!$extendedAttributes->{"LegacyNoInterfaceObject"} && (!$isCallbackInterface || containsInterfaceWithConstantsFromIDL($idlFile))) {
my $exposedAttribute = $extendedAttributes->{"Exposed"};
if (!$exposedAttribute) {
die "ERROR: No [Exposed] extended attribute specified for interface in $idlFileName";
}
if ($exposedAttribute eq "*") {
$exposedAttribute = "(" . join(',', @supportedGlobalContexts) . ")";
}
$exposedAttribute = substr($exposedAttribute, 1, -1) if substr($exposedAttribute, 0, 1) eq "(";
my @globalContexts = split(",", $exposedAttribute);
foreach my $globalContext (@globalContexts) {
my ($attributeCode, $windowAliases) = GenerateConstructorAttributes($interfaceName, $extendedAttributes, $globalContext);
if ($globalContext eq "Window") {
$windowConstructorsCode .= $attributeCode;
$windowConstructorsCode .= $windowAliases if $windowAliases;
} elsif ($globalContext eq "Worker") {
$workerGlobalScopeConstructorsCode .= $attributeCode;
} elsif ($globalContext eq "ShadowRealm") {
$shadowRealmGlobalScopeConstructorsCode .= $attributeCode;
} elsif ($globalContext eq "DedicatedWorker") {
$dedicatedWorkerGlobalScopeConstructorsCode .= $attributeCode;
} elsif ($globalContext eq "ServiceWorker") {
$serviceWorkerGlobalScopeConstructorsCode .= $attributeCode;
} elsif ($globalContext eq "SharedWorker") {
$sharedWorkerGlobalScopeConstructorsCode .= $attributeCode;
} elsif ($globalContext eq "Worklet") {
$workletGlobalScopeConstructorsCode .= $attributeCode;
} elsif ($globalContext eq "PaintWorklet") {
$paintWorkletGlobalScopeConstructorsCode .= $attributeCode;
} elsif ($globalContext eq "AudioWorklet") {
$audioWorkletGlobalScopeConstructorsCode .= $attributeCode;
} elsif ($globalContext eq $testGlobalContextName) {
$testGlobalScopeConstructorsCode .= $attributeCode;
} else {
die "Unsupported global context '$globalContext' used in [Exposed] at $idlFileName";
}
}
}
$constructorsHeaderCode .= " ${interfaceName},\n";
push(@constructors, "${interfaceName}");
if ($extendedAttributes->{LegacyFactoryFunction}) {
$constructorsHeaderCode .= " ${interfaceName}LegacyFactory,\n";
push(@constructors, "${interfaceName}LegacyFactory");
}
}
# Generate partial interfaces for Constructors.
GeneratePartialInterface("DOMWindow", $windowConstructorsCode, $windowConstructorsFile);
GeneratePartialInterface("WorkerGlobalScope", $workerGlobalScopeConstructorsCode, $workerGlobalScopeConstructorsFile);
GeneratePartialInterface("ShadowRealmGlobalScope", $shadowRealmGlobalScopeConstructorsCode, $shadowRealmGlobalScopeConstructorsFile);
GeneratePartialInterface("DedicatedWorkerGlobalScope", $dedicatedWorkerGlobalScopeConstructorsCode, $dedicatedWorkerGlobalScopeConstructorsFile);
GeneratePartialInterface("ServiceWorkerGlobalScope", $serviceWorkerGlobalScopeConstructorsCode, $serviceWorkerGlobalScopeConstructorsFile);
GeneratePartialInterface("SharedWorkerGlobalScope", $sharedWorkerGlobalScopeConstructorsCode, $sharedWorkerGlobalScopeConstructorsFile);
GeneratePartialInterface("WorkletGlobalScope", $workletGlobalScopeConstructorsCode, $workletGlobalScopeConstructorsFile);
GeneratePartialInterface("PaintWorkletGlobalScope", $paintWorkletGlobalScopeConstructorsCode, $paintWorkletGlobalScopeConstructorsFile);
GeneratePartialInterface("AudioWorkletGlobalScope", $audioWorkletGlobalScopeConstructorsCode, $audioWorkletGlobalScopeConstructorsFile);
GeneratePartialInterface($testGlobalContextName, $testGlobalScopeConstructorsCode, $testGlobalScopeConstructorsFile) if defined($testGlobalContextName);
if ($isoSubspacesHeaderFile) {
$isoSubspacesHeaderCode .= "};\n";
$isoSubspacesHeaderCode .= "} // namespace WebCore\n";
WriteFileIfChanged($isoSubspacesHeaderFile, $isoSubspacesHeaderCode);
}
if ($constructorsHeaderFile) {
my $constructorsLength = @constructors;
$constructorsHeaderCode .= "};\n";
$constructorsHeaderCode .= "\n";
$constructorsHeaderCode .= "static constexpr unsigned numberOfDOMConstructors = $constructorsLength;\n";
$constructorsHeaderCode .= "\n";
$constructorsHeaderCode .= "class DOMConstructors {\n";
$constructorsHeaderCode .= " WTF_MAKE_NONCOPYABLE(DOMConstructors);\n";
$constructorsHeaderCode .= " WTF_MAKE_FAST_ALLOCATED(DOMConstructors);\n";
$constructorsHeaderCode .= "public:\n";
$constructorsHeaderCode .= " using ConstructorArray = std::array<JSC::WriteBarrier<JSC::JSObject>, numberOfDOMConstructors>;\n";
$constructorsHeaderCode .= " DOMConstructors() = default;\n";
$constructorsHeaderCode .= " ConstructorArray& array() { return m_array; }\n";
$constructorsHeaderCode .= " const ConstructorArray& array() const { return m_array; }\n";
$constructorsHeaderCode .= "\n";
$constructorsHeaderCode .= "private:\n";
$constructorsHeaderCode .= " ConstructorArray m_array { };\n";
$constructorsHeaderCode .= "};\n";
$constructorsHeaderCode .= "\n";
$constructorsHeaderCode .= "} // namespace WebCore\n";
WriteFileIfChanged($constructorsHeaderFile, $constructorsHeaderCode);
}
# Resolves partial interfaces and include dependencies.
foreach my $idlFilePath (sort keys %supplementalDependencies) {
my $baseFiles = $supplementalDependencies{$idlFilePath};
foreach my $baseFile (@{$baseFiles}) {
my $targetIdlFilePath = $interfaceNameToIdlFilePath{$baseFile} or die "${baseFile}.idl not found, but it is supplemented by $idlFilePath";
push(@{$supplementals{$targetIdlFilePath}}, $idlFilePath);
}
}
# Outputs the dependency.
# The format of a supplemental dependency file:
#
# DOMWindow.idl P.idl Q.idl R.idl
# Document.idl S.idl
# Event.idl
# ...
#
# The above indicates that DOMWindow.idl is supplemented by P.idl, Q.idl and R.idl,
# Document.idl is supplemented by S.idl, and Event.idl is supplemented by no IDLs.
my $dependencies = "";
foreach my $idlFilePath (sort keys %supplementals) {
$dependencies .= "$idlFilePath @{$supplementals{$idlFilePath}}\n";
}
WriteFileIfChanged($supplementalDependencyFile, $dependencies);
sub RemoveWellKnownPrefix
{
my $path = shift;
chomp(my $pwd = `pwd`);
my $srcroot = $ENV{'SRCROOT'};
my $sdkroot = $ENV{'SDKROOT'};
my $built_products_dir = $ENV{'BUILT_PRODUCTS_DIR'};
if ($srcroot and $sdkroot and $built_products_dir) {
$path =~ s/^${pwd}/./;
$path =~ s/^${srcroot}/WebCore/;
$path =~ s/^${sdkroot}/\$(SDKROOT)/;
$path =~ s/^${built_products_dir}/\$(BUILT_PRODUCTS_DIR)/;
} else {
$path = basename($path);
}
return $path;
}
if ($supplementalMakefileDeps) {
my $makefileDeps = "# Supplemental dependencies\n";
foreach my $idlFilePath (sort keys %supplementals) {
my $basename = $idlFilePathToInterfaceName{$idlFilePath};
my @dependencyList = map { RemoveWellKnownPrefix($_) } @{$supplementals{$idlFilePath}};
my @dependencies = sort(keys %{{ map{$_=>1}@dependencyList}});
$makefileDeps .= "JS${basename}.h: @{dependencies}\n";
$makefileDeps .= "DOM${basename}.h: @{dependencies}\n";
$makefileDeps .= "WebDOM${basename}.h: @{dependencies}\n";
foreach my $dependency (@dependencies) {
$makefileDeps .= "${dependency}:\n";
}
}
$makefileDeps .= "# Dictionaries dependencies\n";
foreach my $derivedDictionary (sort keys %dictionaryDependencies) {
$makefileDeps .= "JS${derivedDictionary}.cpp: $dictionaryDependencies{$derivedDictionary}\n";
}
WriteFileIfChanged($supplementalMakefileDeps, $makefileDeps);
}
sub CygwinPathIfNeeded
{
my $path = shift;
return Cygwin::win_to_posix_path($path) if ($^O eq 'cygwin');
return $path;
}
sub WriteFileIfChanged
{
my $fileName = shift;
my $contents = shift;
if (-f $fileName) {
open FH, "<", $fileName or die "Couldn't open $fileName: $!\n";
my @lines = <FH>;
my $oldContents = join "", @lines;
close FH;
return if $contents eq $oldContents;
}
open FH, ">", $fileName or die "Couldn't open $fileName: $!\n";
print FH $contents;
close FH;
}
sub GeneratePartialInterface
{
my $interfaceName = shift;
my $attributesCode = shift;
my $destinationFile = shift;
my $contents = "partial interface ${interfaceName} {\n$attributesCode};\n";
WriteFileIfChanged($destinationFile, $contents);
my $fullPath = Cwd::realpath($destinationFile);
$supplementalDependencies{$fullPath} = [$interfaceName] if $interfaceNameToIdlFilePath{$interfaceName};
}
sub GenerateConstructorAttributes
{
my $interfaceName = shift;
my $extendedAttributes = shift;
my $globalContext = shift;
# FIXME: Rather than being ConditionalForWorker=FOO, we need a syntax like ConditionalForContext=(Worker:FOO).
if ($extendedAttributes->{"ConditionalForWorker"} && $globalContext eq "Worker") {
my $conditionalForWorker = $extendedAttributes->{"ConditionalForWorker"};
my $existingConditional = $extendedAttributes->{"Conditional"};
if ($existingConditional) {
$existingConditional .= "&" . $conditionalForWorker;
} else {
$existingConditional = $conditionalForWorker;
}
$extendedAttributes->{"Conditional"} = $existingConditional;
}
my $code = " ";
my @extendedAttributesList;
foreach my $attributeName (sort keys %{$extendedAttributes}) {
next unless ($attributeName eq "Conditional" || $attributeName eq "EnabledAtRuntime" || $attributeName eq "EnabledForWorld"
|| $attributeName eq "EnabledBySetting" || $attributeName eq "SecureContext" || $attributeName eq "PrivateIdentifier"
|| $attributeName eq "PublicIdentifier" || $attributeName eq "DisabledByQuirk" || $attributeName eq "EnabledByQuirk"
|| $attributeName eq "EnabledForContext") || $attributeName eq "LegacyFactoryFunctionEnabledBySetting";
my $extendedAttribute = $attributeName;
$extendedAttribute .= "=" . $extendedAttributes->{$attributeName} unless $extendedAttributes->{$attributeName} eq "VALUE_IS_MISSING";
push(@extendedAttributesList, $extendedAttribute);
}
$code .= "[" . join(', ', @extendedAttributesList) . "] " if @extendedAttributesList;
my $originalInterfaceName = $interfaceName;
$interfaceName = $extendedAttributes->{"InterfaceName"} if $extendedAttributes->{"InterfaceName"};
$code .= "attribute " . $originalInterfaceName . "Constructor $interfaceName;\n";
# In addition to the regular property, for every [LegacyFactoryFunction] extended attribute on an interface,
# a corresponding property MUST exist on the ECMAScript global object.
if ($extendedAttributes->{"LegacyFactoryFunction"}) {
my $constructorName = $extendedAttributes->{"LegacyFactoryFunction"};
$constructorName =~ s/\(.*//g; # Extract function name.
$code .= " ";
$code .= "[" . join(', ', @extendedAttributesList) . "] " if @extendedAttributesList;
$code .= "attribute " . $originalInterfaceName . "LegacyFactoryFunctionConstructor $constructorName;\n";
}
my $windowAliasesCode;
if ($extendedAttributes->{"LegacyWindowAlias"}) {
my $attributeValue = $extendedAttributes->{"LegacyWindowAlias"};
$attributeValue = substr($attributeValue, 1, -1) if substr($attributeValue, 0, 1) eq "(";
my @windowAliases = split(",", $attributeValue);
foreach my $windowAlias (@windowAliases) {
$windowAliasesCode .= " ";
$windowAliasesCode .= "[" . join(', ', @extendedAttributesList) . "] " if @extendedAttributesList;
$windowAliasesCode .= "attribute " . $originalInterfaceName . "Constructor $windowAlias; // Legacy Window alias.\n";
}
}
return ($code, $windowAliasesCode);
}
sub trim
{
my $string = shift;
$string =~ s/^\s+|\s+$//g;
return $string;
}
sub listsAreIdentical
{
my ($list1, $list2) = @_;
if (scalar(@{$list1}) != scalar(@{$list2})) {
return 0;
}
for my $i (0 .. $#{$list1}) {
if ($list1->[$i] ne $list2->[$i]) {
return 0;
}
}
return 1;
}
sub processIDL
{
my $fileName = shift;
my $filePath = shift;
my $idlFile = IDLFile->new();
$idlFile->fileName($fileName);
my $primaryDeclarationName = fileparse(basename($fileName), ".idl");
$idlFile->primaryDeclarationName($primaryDeclarationName);
open my $file, "<", $filePath or die "Could not open $filePath for reading: $!";
my @lines = <$file>;
close $file;
# Filter out preprocessor lines.
@lines = grep(!/^\s*#/, @lines);
# Remove comments from fileContents before processing.
# FIX: Preference to use Regex::Common::comment, however it is not available on
# all build systems.
my $fileContents = join('', @lines);
$fileContents =~ s/(?:(?:(?:\/\/)(?:[^\n]*)(?:\n))|(?:(?:\/\*)(?:(?:[^\*]+|\*(?!\/))*)(?:\*\/)))//g;
$idlFile->fileContents($fileContents);
if ($validateAgainstParser) {
my $parser = IDLParser->new(1);
$idlFile->parsedDocument($parser->Parse($filePath, $defines, $preprocessor, $idlAttributes));
}
return $idlFile;
}
sub getPartialNamesFromIDL
{
my $idlFile = shift;
my $fileContents = $idlFile->fileContents;
my @partialNames = ();
while ($fileContents =~ /partial\s+interface\s+mixin\s+(\w+)/mg) {
push(@partialNames, $1);
}
$fileContents = $idlFile->fileContents;
while ($fileContents =~ /partial\s+(interface|dictionary|namespace)\s+(\w+)/mg) {
push(@partialNames, $2) if $2 ne "mixin";
}
if ($validateAgainstParser) {
print "Validating getPartialNamesFromIDL for " . $idlFile->fileName . " against validation parser.\n" if $verbose;
my @partialsFromParsedDocument = ();
foreach my $interface (@{$idlFile->parsedDocument->interfaces}) {
push(@partialsFromParsedDocument, $interface->type->name) if $interface->isPartial;
}
foreach my $dictionary (@{$idlFile->parsedDocument->dictionaries}) {
push(@partialsFromParsedDocument, $dictionary->type->name) if $dictionary->isPartial;
}
foreach my $namespace (@{$idlFile->parsedDocument->namespaces}) {
push(@partialsFromParsedDocument, $namespace->name) if $namespace->isPartial;
}
my @sortedPartialNames = sort @partialNames;
my @sortedPartialsFromParsedDocument = sort @partialsFromParsedDocument;
local $Data::Dumper::Terse = 1;
local $Data::Dumper::Indent = 0;
unless (listsAreIdentical(\@sortedPartialNames, \@sortedPartialsFromParsedDocument)) {
die "FAILURE: Partial declarations from regular expression based parser (" . Dumper(@sortedPartialNames) . ") don't match those from validation parser (" . Dumper(@sortedPartialsFromParsedDocument) . ") [" . $idlFile->fileName . "].";
}
print "SUCCESS! Partial declarations from regular expression based parser (" . Dumper(@sortedPartialNames) . ") match those from validation parser (" . Dumper(@sortedPartialsFromParsedDocument) . ").\n" if $verbose;
}
return \@partialNames;
}
# identifier-A includes identifier-B;
# https://webidl.spec.whatwg.org/#includes-statement
sub getIncludedInterfacesFromIDL
{
my $idlFile = shift;
my $interfaceName = shift;
my $fileContents = $idlFile->fileContents;
my @includedInterfaces = ();
while ($fileContents =~ /\b(\w+)\s+includes\s+(\w+)\s*;/mg) {
die "Identifier on the left of the 'includes' statement should be $interfaceName in $interfaceName.idl, but found $1" if $1 ne $interfaceName;
push(@includedInterfaces, $2);
}
if ($validateAgainstParser) {
print "Validating getIncludedInterfacesFromIDL for " . $idlFile->fileName . " against validation parser.\n" if $verbose;
my @includedInterfacesFromParsedDocument = ();
foreach my $include (@{$idlFile->parsedDocument->includes}) {
push(@includedInterfacesFromParsedDocument, $include->mixinIdentifier);
}
my @sortedIncludedInterfaces = sort @includedInterfaces;
my @sortedIncludedInterfacesFromParsedDocument = sort @includedInterfacesFromParsedDocument;
local $Data::Dumper::Terse = 1;
local $Data::Dumper::Indent = 0;
unless (listsAreIdentical(\@sortedIncludedInterfaces, \@sortedIncludedInterfacesFromParsedDocument)) {
die "FAILURE: Included interfaces from regular expression based parser (" . Dumper(@sortedIncludedInterfaces) . ") don't match those from validation parser (" . Dumper(@sortedIncludedInterfacesFromParsedDocument) . ") [" . $idlFile->fileName . "]";
}
print "SUCCESS! Included interfaces from regular expression based parser (" . Dumper(@sortedIncludedInterfaces) . ") match those from validation parser (" . Dumper(@sortedIncludedInterfacesFromParsedDocument) . ").\n" if $verbose;
}
return \@includedInterfaces
}
sub isCallbackInterfaceFromIDL
{
my $idlFile = shift;
my $fileContents = $idlFile->fileContents;
my $containsCallbackInterface = ($fileContents =~ /callback\s+interface\s+\w+/gs);
if ($validateAgainstParser) {
print "Validating isCallbackInterfaceFromIDL for " . $idlFile->fileName . " against validation parser.\n" if $verbose;
my $containsCallbackInterfaceFromParsedDocument = 0;
foreach my $interface (@{$idlFile->parsedDocument->interfaces}) {
if ($interface->isCallback) {
$containsCallbackInterfaceFromParsedDocument = 1;
last;
}
}
unless ($containsCallbackInterface == $containsCallbackInterfaceFromParsedDocument ) {
die "FAILURE: Determination of whether there is a callback interface from regular expression based parser (" . ($containsCallbackInterface ? "YES" : "NO") . ") doesn't match the determination from the validation parser (" . ($containsCallbackInterfaceFromParsedDocument ? "YES" : "NO") . ") [" . $idlFile->fileName . "].";
}
print "SUCCESS! Determination of whether there is a callback interface from regular expression based parser (" . ($containsCallbackInterface ? "YES" : "NO") . ") does match the determination from the validation parser (" . ($containsCallbackInterfaceFromParsedDocument ? "YES" : "NO") . ").\n" if $verbose;
}
return $containsCallbackInterface
}
sub isMixinInterfaceFromIDL
{
my $idlFile = shift;
my $fileContents = $idlFile->fileContents;
my $containsMixinInterface = ($fileContents =~ /interface\s+mixin\s+\w+/gs);
if ($validateAgainstParser) {
print "Validating isCallbackInterfaceFromIDL for " . $idlFile->fileName . " against validation parser.\n" if $verbose;
my $containsMixinInterfaceFromParsedDocument = 0;
foreach my $interface (@{$idlFile->parsedDocument->interfaces}) {
if ($interface->isMixin) {
$containsMixinInterfaceFromParsedDocument = 1;
last;
}
}
unless ($containsMixinInterface == $containsMixinInterfaceFromParsedDocument ) {
die "FAILURE: Determination of whether there is a mixin interface from regular expression based parser (" . ($containsMixinInterface ? "YES" : "NO") . ") doesn't match the determination from validation parser (" . ($containsMixinInterfaceFromParsedDocument ? "YES" : "NO") . ") [" . $idlFile->fileName . "].";
}
print "SUCCESS! Determination of whether there is a mixin interface from regular expression based parser (" . ($containsMixinInterface ? "YES" : "NO") . ") does match the determination from validation parser (" . ($containsMixinInterfaceFromParsedDocument ? "YES" : "NO") . ").\n" if $verbose;
}
return $containsMixinInterface;
}
sub containsIterableInterfaceFromIDL
{
my $idlFile = shift;
my $fileContents = $idlFile->fileContents;
my $containsIterableInterface = ($fileContents =~ /iterable\s*<\s*\w+\s*/gs);
if ($validateAgainstParser) {
print "Validating containsIterableInterfaceFromIDL for " . $idlFile->fileName . " against validation parser.\n" if $verbose;
my $containsIterableInterfaceFromParsedDocument = 0;
foreach my $interface (@{$idlFile->parsedDocument->interfaces}) {
if ($interface->iterable) {
$containsIterableInterfaceFromParsedDocument = 1;
last;
}
}
unless ($containsIterableInterface == $containsIterableInterfaceFromParsedDocument ) {
die "FAILURE: Determination of whether there is an iterable interface from regular expression based parser (" . ($containsIterableInterface ? "YES" : "NO") . ") doesn't match the determination from validation parser (" . ($containsIterableInterfaceFromParsedDocument ? "YES" : "NO") . ") [" . $idlFile->fileName . "].";
}
print "SUCCESS! Determination of whether there is an iterable interface from regular expression based parser (" . ($containsIterableInterface ? "YES" : "NO") . ") does match the determination from validation parser (" . ($containsIterableInterfaceFromParsedDocument ? "YES" : "NO") . ").\n" if $verbose;
}
return $containsIterableInterface;
}
sub containsInterfaceOrCallbackInterfaceFromIDL
{
my $idlFile = shift;
my $fileContents = $idlFile->fileContents;
my $containsInterfaceOrCallbackInterface = ($fileContents =~ /\b(callback interface|interface|namespace)\s+(\w+)/gs);
if ($validateAgainstParser) {
print "Validating containsInterfaceOrCallbackInterfaceFromIDL for " . $idlFile->fileName . " against validation parser.\n" if $verbose;
my $containsInterfaceOrCallbackInterfaceFromParsedDocument = (@{$idlFile->parsedDocument->interfaces} > 0);
unless ($containsInterfaceOrCallbackInterface == $containsInterfaceOrCallbackInterfaceFromParsedDocument ) {
die "FAILURE: Determination of whether there is an interface or callback interface from regular expression based parser (" . ($containsInterfaceOrCallbackInterface ? "YES" : "NO") . ") doesn't match the determination from validation parser (" . ($containsInterfaceOrCallbackInterfaceFromParsedDocument ? "YES" : "NO") . ") [" . $idlFile->fileName . "].";
}
print "SUCCESS! Determination of whether there is an interface or callback interface from regular expression based parser (" . ($containsInterfaceOrCallbackInterface ? "YES" : "NO") . ") does match the determination from validation parser (" . ($containsInterfaceOrCallbackInterfaceFromParsedDocument ? "YES" : "NO") . ").\n" if $verbose;
}
return $containsInterfaceOrCallbackInterface;
}
sub containsInterfaceWithConstantsFromIDL
{
my $idlFile = shift;
my $fileContents = $idlFile->fileContents;
my $containsInterfaceWithConstants = ($fileContents =~ /\s+const[\s\w]+=\s+[\w]+;/gs);
if ($validateAgainstParser) {
print "Validating containsInterfaceWithConstantsFromIDL for " . $idlFile->fileName . " against validation parser.\n" if $verbose;
my $containsInterfaceWithConstantsFromParsedDocument = 0;
foreach my $interface (@{$idlFile->parsedDocument->interfaces}) {
if (@{$interface->constants} > 0) {
$containsInterfaceWithConstantsFromParsedDocument = 1;
last;
}
}
unless ($containsInterfaceWithConstants == $containsInterfaceWithConstantsFromParsedDocument ) {
die "FAILURE: Determination of whether there is an interface with constants from regular expression based parser (" . ($containsInterfaceWithConstants ? "YES" : "NO") . ") doesn't match the determination from validation parser (" . ($containsInterfaceWithConstantsFromParsedDocument ? "YES" : "NO") . ") [" . $idlFile->fileName . "].";
}
print "SUCCESS! Determination of whether there is an interface with constants from regular expression based parser (" . ($containsInterfaceWithConstants ? "YES" : "NO") . ") does match the determination from validation parser (" . ($containsInterfaceWithConstantsFromParsedDocument ? "YES" : "NO") . ").\n" if $verbose;
}
return $containsInterfaceWithConstants;
}
sub getInterfaceExtendedAttributesFromIDL
{
my $idlFile = shift;
my $fileContents = $idlFile->fileContents;
my $extendedAttributes = {};
if ($fileContents =~ /\[(.*)\]\s+(callback interface|interface|namespace)\s+(\w+)/gs) {
my $parameters = $1;
if (index($parameters, '}') != -1) {
# In case we have a declaration like a dictionary with extended attributes defined before the interface.
$parameters = (split '}', $1)[-1];
$parameters = substr($parameters, index($parameters, '[') + 1);
}
my @parts = split(m/,(?![^()]*\))/, $parameters);
foreach my $part (@parts) {
my @keyValue = split('=', $part);
my $key = trim($keyValue[0]);
next unless length($key);
my $value = "VALUE_IS_MISSING";
$value = trim($keyValue[1]) if @keyValue > 1;
$extendedAttributes->{$key} = $value;
}
}
if ($validateAgainstParser) {
print "Validating getInterfaceExtendedAttributesFromIDL for " . $idlFile->fileName . " against validation parser.\n" if $verbose;
my $primaryInterface;
foreach my $interface (@{$idlFile->parsedDocument->interfaces}) {
if ($interface->type->name eq $idlFile->primaryDeclarationName) {
$primaryInterface = $interface;
last;
}
}
die "Failed to find primary interface for " . $idlFile->fileName unless $primaryInterface;
# FIXME: Comparing the deep structure of the extended attributes is suitably complex that for now
# we only validate that both parsers produce the same keys.
my @sortedExtendedAttributeKeys = sort keys %{$extendedAttributes};
my @sortedExtendedAttributeKeysFromParsedDocument = sort keys %{$primaryInterface->extendedAttributes};
local $Data::Dumper::Terse = 1;
local $Data::Dumper::Indent = 0;
unless (listsAreIdentical(\@sortedExtendedAttributeKeys, \@sortedExtendedAttributeKeysFromParsedDocument)) {
die "FAILURE: Extended attributes for the primary interface from regular expression based parser (" . Dumper(@sortedExtendedAttributeKeys) . ") don't match those from validation parser (" . Dumper(@sortedExtendedAttributeKeysFromParsedDocument) . ") [" . $idlFile->fileName . "]";
}
print "SUCCESS! Extended attributes for the primary interface from regular expression based parser (" . Dumper(@sortedExtendedAttributeKeys) . ") match those from validation parser (" . Dumper(@sortedExtendedAttributeKeysFromParsedDocument) . ").\n" if $verbose;
}
return $extendedAttributes;
}
sub getUndefinedBaseDictionariesFromIDL
{
my $idlFile = shift;
my $fileContents = $idlFile->fileContents;
my @dictionaryNames = ();
while ($fileContents =~ /\s*dictionary\s+(\w+)\s*/mg) {
push(@dictionaryNames, $1)
}
$fileContents = $idlFile->fileContents;
my @baseDictionaries = ();
while ($fileContents =~ /\s*dictionary\s+(\w+)\s+:\s+(\w+)\s*/mg) {
next if (grep { $_ eq $2 } @dictionaryNames);
next if (grep { $_ eq $2 } @baseDictionaries);
push(@baseDictionaries, $2);
}
if ($validateAgainstParser) {
print "Validating getUndefinedBaseDictionariesFromIDL for " . $idlFile->fileName . " against validation parser.\n" if $verbose;
my @dictionaryNamesFromParsedDocument = ();
foreach my $dictionary (@{$idlFile->parsedDocument->dictionaries}) {
push(@dictionaryNamesFromParsedDocument, $dictionary->type->name);
}
my @baseDictionariesFromParsedDocument = ();
foreach my $dictionary (@{$idlFile->parsedDocument->dictionaries}) {
# Skip dictionaries without a base type.
next if !$dictionary->parentType;
# Skip dictionaries that have their base type defined in this document.
next if (grep { $_ eq $dictionary->parentType->name } @dictionaryNamesFromParsedDocument);
# Skip dictionaries that have already been added to the list.
next if (grep { $_ eq $dictionary->parentType->name } @baseDictionariesFromParsedDocument);
push(@baseDictionariesFromParsedDocument, $dictionary->parentType->name);
}
my @sortedBaseDictionaries = sort @baseDictionaries;
my @sortedBaseDictionariesFromParsedDocument = sort @baseDictionariesFromParsedDocument;
local $Data::Dumper::Terse = 1;
local $Data::Dumper::Indent = 0;
unless (listsAreIdentical(\@sortedBaseDictionaries, \@sortedBaseDictionariesFromParsedDocument)) {
die "FAILURE: Undefined base dictionaries from regular expression based parser (" . Dumper(@sortedBaseDictionaries) . ") don't match those from validation parser (" . Dumper(@sortedBaseDictionariesFromParsedDocument) . ") [" . $idlFile->fileName . "]";
}
print "SUCCESS! Undefined base dictionaries from regular expression based parser (" . Dumper(@sortedBaseDictionaries) . ") match those from validation parser (" . Dumper(@sortedBaseDictionariesFromParsedDocument) . ").\n" if $verbose;
}
return \@baseDictionaries;
}