# # WebKit IDL parser # # Copyright (C) 2005 Nikolas Zimmermann # Copyright (C) 2006 Samuel Weinig # Copyright (C) 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. # Copyright (C) 2009 Cameron McCormack # Copyright (C) Research In Motion Limited 2010. All rights reserved. # Copyright (C) 2013 Samsung Electronics. 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. # package CodeGenerator; use strict; use File::Basename; use File::Find; use Carp qw; use Data::Dumper; my $useDocument = ""; my $useGenerator = ""; my $useOutputDir = ""; my $useOutputHeadersDir = ""; my $useDirectories = ""; my $preprocessor; my $idlAttributes; my $writeDependencies = 0; my $defines = ""; my $targetIdlFilePath = ""; my $supplementalDependencies; my $codeGenerator = 0; my $verbose = 0; my %integerTypeHash = ( "byte" => 1, "long long" => 1, "long" => 1, "octet" => 1, "short" => 1, "unsigned long long" => 1, "unsigned long" => 1, "unsigned short" => 1, ); my %floatingPointTypeHash = ( "float" => 1, "unrestricted float" => 1, "double" => 1, "unrestricted double" => 1, ); my %stringTypeHash = ( "ByteString" => 1, "DOMString" => 1, "USVString" => 1, ); my %bufferSourceTypes = ( "ArrayBuffer" => 1, "ArrayBufferView" => 1, "DataView" => 1, "Float32Array" => 1, "Float64Array" => 1, "Int16Array" => 1, "Int32Array" => 1, "Int8Array" => 1, "Uint16Array" => 1, "Uint32Array" => 1, "Uint8Array" => 1, "Uint8ClampedArray" => 1, ); my %primitiveTypeHash = ( "boolean" => 1, "undefined" => 1, "Date" => 1 ); my %dictionaryTypeImplementationNameOverrides = (); my %enumTypeImplementationNameOverrides = (); my %svgAttributesInHTMLHash = ( "class" => 1, "id" => 1, "onabort" => 1, "onclick" => 1, "onerror" => 1, "onload" => 1, "onmousedown" => 1, "onmouseenter" => 1, "onmouseleave" => 1, "onmousemove" => 1, "onmouseout" => 1, "onmouseover" => 1, "onmouseup" => 1, "onresize" => 1, "onscroll" => 1, "onunload" => 1, ); # Cache of IDL file pathnames. my $idlFiles; my $cachedInterfaces = {}; my $cachedExternalDictionaries = {}; my $cachedExternalEnumerations = {}; my $cachedTypes = {}; sub assert { my $message = shift; my $mess = longmess(); print Dumper($mess); die $message; } # Default constructor sub new { my $object = shift; my $reference = { }; $useDirectories = shift; $useGenerator = shift; $useOutputDir = shift; $useOutputHeadersDir = shift; $preprocessor = shift; $writeDependencies = shift; $verbose = shift; $targetIdlFilePath = shift; $idlAttributes = shift; $supplementalDependencies = shift; bless($reference, $object); return $reference; } sub ProcessDocument { my $object = shift; $useDocument = shift; $defines = shift; my $ifaceName = "CodeGenerator" . $useGenerator; require $ifaceName . ".pm"; # Dynamically load external code generation perl module $codeGenerator = $ifaceName->new($object, $writeDependencies, $verbose, $targetIdlFilePath); unless (defined($codeGenerator)) { my $interfaces = $useDocument->interfaces; foreach my $interface (@$interfaces) { print "Skipping $useGenerator code generation for IDL interface \"" . $interface->type->name . "\".\n" if $verbose; } return; } if (@{$useDocument->interfaces}) { $object->ProcessInterfaces($useDocument, $defines, $codeGenerator); return; } if (@{$useDocument->callbackFunctions}) { $object->ProcessCallbackFunctions($useDocument, $defines, $codeGenerator); return; } if (@{$useDocument->dictionaries}) { $object->ProcessDictionaries($useDocument, $defines, $codeGenerator); return; } if (@{$useDocument->enumerations}) { $object->ProcessEnumerations($useDocument, $defines, $codeGenerator); return; } die "Processing document " . $useDocument->fileName . " did not generate anything."; } sub GenerateEmptyHeaderAndCpp { my ($object, $useDocument, $codeGenerator) = @_; my $targetName = fileparse($useDocument->fileName, ".idl"); my $prefix = $codeGenerator->FileNamePrefix(); my $headerName = "${prefix}${targetName}.h"; my $cppName = "${prefix}${targetName}.cpp"; my $contents = "/* This file is generated to inform build scripts that ${headerName} and ${cppName} were created for ${targetName}.idl, and prevent the build scripts from trying to regenerate ${headerName} and ${cppName} on every build. */ "; open FH, "> ${useOutputHeadersDir}/${headerName}" or die "Cannot open ${headerName}\n"; print FH $contents; close FH; open FH, "> ${useOutputDir}/${cppName}" or die "Cannot open ${cppName}\n"; print FH $contents; close FH; } sub ProcessDictionaryAndEnumerationImplementedAsOverrides { my ($object, $useDocument) = @_; foreach my $dictionary (@{$useDocument->dictionaries}) { if ($dictionary->extendedAttributes->{"ImplementedAs"}) { $dictionaryTypeImplementationNameOverrides{$dictionary->type->name} = $dictionary->extendedAttributes->{"ImplementedAs"}; } } foreach my $enumeration (@{$useDocument->enumerations}) { if ($enumeration->extendedAttributes->{"ImplementedAs"}) { $enumTypeImplementationNameOverrides{$enumeration->type->name} = $enumeration->extendedAttributes->{"ImplementedAs"}; } } } sub ProcessInterfaces { my ($object, $useDocument, $defines, $codeGenerator) = @_; die "Multiple interfaces per document are not supported" if @{$useDocument->interfaces} > 1; my $interface = $useDocument->interfaces->[0]; if ($interface->isMixin || $interface->isPartial) { $object->GenerateEmptyHeaderAndCpp($useDocument, $codeGenerator); return; } $object->ProcessInterfaceSupplementalDependencies($interface, $useDocument); $object->ProcessDictionaryAndEnumerationImplementedAsOverrides($useDocument); print "Generating $useGenerator bindings code for IDL interface \"" . $interface->type->name . "\"\n" if $verbose; $codeGenerator->GenerateInterface($interface, $defines, $useDocument->enumerations, $useDocument->dictionaries); $codeGenerator->WriteData($interface, $useOutputDir, $useOutputHeadersDir); } sub ProcessCallbackFunctions { my ($object, $useDocument, $defines, $codeGenerator) = @_; die "Multiple standalone callback functions per document are not supported" if @{$useDocument->callbackFunctions} > 1; my $callbackFunction = $useDocument->callbackFunctions->[0]; $object->ProcessDictionaryAndEnumerationImplementedAsOverrides($useDocument); print "Generating $useGenerator bindings code for IDL callback function \"" . $callbackFunction->type->name . "\"\n" if $verbose; $codeGenerator->GenerateCallbackFunction($callbackFunction, $useDocument->enumerations, $useDocument->dictionaries); $codeGenerator->WriteData($callbackFunction, $useOutputDir, $useOutputHeadersDir); } sub ProcessDictionaries { my ($object, $useDocument, $defines, $codeGenerator) = @_; my $dictionary; my $otherDictionaries; if (@{$useDocument->dictionaries} == 1) { $dictionary = $useDocument->dictionaries->[0]; } else { my $primaryDictionaryName = fileparse($targetIdlFilePath, ".idl"); foreach my $candidate (@{$useDocument->dictionaries}) { if ($candidate->type->name eq $primaryDictionaryName) { $dictionary = $candidate; } else { push(@$otherDictionaries, $candidate); } } die "Multiple dictionaries per document are only supported if one matches the filename" unless $dictionary; } if ($dictionary->isPartial) { $object->GenerateEmptyHeaderAndCpp($useDocument, $codeGenerator); return; } $object->ProcessDictionarySupplementalDependencies($dictionary, $useDocument); $object->ProcessDictionaryAndEnumerationImplementedAsOverrides($useDocument); print "Generating $useGenerator bindings code for IDL dictionary \"" . $dictionary->type->name . "\"\n" if $verbose; $codeGenerator->GenerateDictionary($dictionary, $useDocument->enumerations, $otherDictionaries); $codeGenerator->WriteData($dictionary, $useOutputDir, $useOutputHeadersDir); } sub ProcessEnumerations { my ($object, $useDocument, $defines, $codeGenerator) = @_; die "Multiple standalone enumerations per document are not supported" if @{$useDocument->enumerations} > 1; my $enumeration = $useDocument->enumerations->[0]; $object->ProcessDictionaryAndEnumerationImplementedAsOverrides($useDocument); print "Generating $useGenerator bindings code for IDL enumeration \"" . $enumeration->type->name . "\"\n" if $verbose; $codeGenerator->GenerateEnumeration($enumeration); $codeGenerator->WriteData($enumeration, $useOutputDir, $useOutputHeadersDir); } sub MergeExtendedAttributesFromSupplemental { my ($object, $supplementalExtendedAttributes, $property, $context) = @_; foreach my $extendedAttributeName (keys %{$supplementalExtendedAttributes}) { my $isAllowed = 0; for my $contextAllowed (@{$idlAttributes->{$extendedAttributeName}->{"contextsAllowed"}}) { if ($contextAllowed eq $context) { $isAllowed = 1; last; } } next unless $isAllowed; # FIXME: Tests currently depend on special case for Conditional overwriting non-operation cases, but this should # be fixed. Ultimately, this will probably mean either removing support for disjunctions for Conditional, # which is not used outside of the tests, or adding support for parentheses to disambiguate. if ($extendedAttributeName eq "Conditional" && $context ne "operation") { $property->extendedAttributes->{$extendedAttributeName} = $supplementalExtendedAttributes->{$extendedAttributeName}; next; } # Handle case that the attribute already has a extented attribute with this key. if ($property->extendedAttributes->{$extendedAttributeName}) { if (!$idlAttributes->{$extendedAttributeName}->{"supportsConjunction"}) { die "Duplicate non-mergeable extended attribute ($extendedAttributeName) found when merging extended attributes for ${property->name}"; } $property->extendedAttributes->{$extendedAttributeName} = $property->extendedAttributes->{$extendedAttributeName} . "&" . $supplementalExtendedAttributes->{$extendedAttributeName}; } else { $property->extendedAttributes->{$extendedAttributeName} = $supplementalExtendedAttributes->{$extendedAttributeName}; } } } sub IsValidSupplementalInterface { my ($object, $supplementalInterface, $targetInterface, $includesMap) = @_; return 1 if $supplementalInterface->isPartial && $supplementalInterface->type->name eq $targetInterface->type->name; return 1 if $supplementalInterface->isMixin && $includesMap->{$supplementalInterface->type->name}; return 0; } sub IsValidSupplementalDictionary { my ($object, $supplementalDictionary, $targetDictionary) = @_; return 1 if $supplementalDictionary->isPartial && $supplementalDictionary->type->name eq $targetDictionary->type->name; return 0; } sub ProcessInterfaceSupplementalDependencies { my ($object, $targetInterface, $targetDocument) = @_; return unless $supplementalDependencies; my $targetFileName = fileparse($targetDocument->fileName); # FIXME: Currently, we require all the includes statements to be in the # same file as the interface they are being mixed into, but that restriction # is artificial and we could add support for includes statements elsewhere. my %includesMap = (); foreach my $include (@{$targetDocument->includes}) { if ($include->interfaceIdentifier eq $targetInterface->type->name) { unless (grep { fileparse($_, ".idl") eq $include->mixinIdentifier } @{$supplementalDependencies->{$targetFileName}}) { die "included interface mixin " . $include->mixinIdentifier . " not in supplemental dependencies." } $includesMap{$include->mixinIdentifier} = $include; } } foreach my $idlFile (@{$supplementalDependencies->{$targetFileName}}) { next if fileparse($idlFile) eq $targetFileName; my $interfaceName = fileparse($idlFile, ".idl"); my $parser = IDLParser->new(!$verbose); my $document = $parser->Parse($idlFile, $defines, $preprocessor, $idlAttributes); foreach my $interface (@{$document->interfaces}) { next unless $object->IsValidSupplementalInterface($interface, $targetInterface, \%includesMap); if ($interface->isMixin && !$interface->isPartial) { # Recursively process any supplemental dependencies for each valid mixin. This # allows partial partial interface mixins to be merged into the mixin. $object->ProcessInterfaceSupplementalDependencies($interface, $document); } # Support for attributes from supplemental interfaces. foreach my $attribute (@{$interface->attributes}) { next unless $targetInterface->isMixin || shouldPropertyBeExposed($attribute, $targetInterface); if ($interface->isMixin && !$interface->isPartial) { # Add includes statement specific extended attributes to each attribute. $object->MergeExtendedAttributesFromSupplemental($includesMap{$interface->type->name}->extendedAttributes, $attribute, "attribute"); } # Add interface-wide extended attributes to each attribute. $object->MergeExtendedAttributesFromSupplemental($interface->extendedAttributes, $attribute, "attribute"); push(@{$targetInterface->attributes}, $attribute); } # Support for operations from supplemental interfaces. foreach my $operation (@{$interface->operations}) { next unless $targetInterface->isMixin || shouldPropertyBeExposed($operation, $targetInterface); if ($interface->isMixin && !$interface->isPartial) { # Add includes statement specific extended attributes to each operation. $object->MergeExtendedAttributesFromSupplemental($includesMap{$interface->type->name}->extendedAttributes, $operation, "operation"); } # Add interface-wide extended attributes to each operation. $object->MergeExtendedAttributesFromSupplemental($interface->extendedAttributes, $operation, "operation"); push(@{$targetInterface->operations}, $operation); } # Support for constants from supplemental interfaces. foreach my $constant (@{$interface->constants}) { next unless $targetInterface->isMixin || shouldPropertyBeExposed($constant, $targetInterface); if ($interface->isMixin && !$interface->isPartial) { # Add includes statement specific extended attributes to each operation. $object->MergeExtendedAttributesFromSupplemental($includesMap{$interface->type->name}->extendedAttributes, $constant, "constant"); } # Add interface-wide extended attributes to each constant. $object->MergeExtendedAttributesFromSupplemental($interface->extendedAttributes, $constant, "constant"); push(@{$targetInterface->constants}, $constant); } } } } sub ProcessDictionarySupplementalDependencies { my ($object, $targetDictionary, $targetDocument) = @_; return unless $supplementalDependencies; my $targetFileName = fileparse($targetDocument->fileName); foreach my $idlFile (@{$supplementalDependencies->{$targetFileName}}) { next if fileparse($idlFile) eq $targetFileName; my $dictionaryName = fileparse($idlFile, ".idl"); my $parser = IDLParser->new(!$verbose); my $document = $parser->Parse($idlFile, $defines, $preprocessor, $idlAttributes); foreach my $dictionary (@{$document->dictionaries}) { next unless $object->IsValidSupplementalDictionary($dictionary, $targetDictionary); # Support for members of partial dictionaries. foreach my $member (@{$dictionary->members}) { next unless shouldPropertyBeExposed($member, $targetDictionary); # Add interface-wide extended attributes to each member. $object->MergeExtendedAttributesFromSupplemental($dictionary->extendedAttributes, $member, "dictionary-member"); push(@{$targetDictionary->members}, $member); } } } } # Attributes / Operations / Constants of supplemental interfaces can have an [Exposed=XX] attribute which restricts # on which global contexts they should be exposed. sub shouldPropertyBeExposed { my ($context, $target) = @_; my $exposedAttribute = $target->extendedAttributes->{"Exposed"} || "Window"; $exposedAttribute = substr($exposedAttribute, 1, -1) if substr($exposedAttribute, 0, 1) eq "("; my @targetGlobalContexts = split(",", $exposedAttribute); my $exposed = $context->extendedAttributes->{Exposed}; return 1 unless $exposed; $exposed = substr($exposed, 1, -1) if substr($exposed, 0, 1) eq "("; my @sourceGlobalContexts = split(",", $exposed); foreach my $targetGlobalContext (@targetGlobalContexts) { return 1 if grep(/^$targetGlobalContext$/, @sourceGlobalContexts); } return 0; } sub UpdateFile { my $object = shift; my $fileName = shift; my $contents = shift; # FIXME: We should only write content if it is different from what is in the file. # But that would mean running more often the binding generator, see https://bugs.webkit.org/show_bug.cgi?id=131756 open FH, ">", $fileName or die "Couldn't open $fileName: $!\n"; print FH $contents; close FH; } sub ForAllParents { my $object = shift; my $interface = shift; my $beforeRecursion = shift; my $afterRecursion = shift; my $recurse; $recurse = sub { my $outerInterface = shift; my $currentInterface = shift; if ($currentInterface->parentType) { my $interfaceName = $currentInterface->parentType->name; my $parentInterface = $object->ParseInterface($outerInterface, $interfaceName); if ($beforeRecursion) { &$beforeRecursion($parentInterface) eq 'prune' and next; } &$recurse($outerInterface, $parentInterface); &$afterRecursion($parentInterface) if $afterRecursion; } }; &$recurse($interface, $interface); } sub IDLFileForInterface { my $object = shift; my $interfaceName = shift; unless ($idlFiles) { my $sourceRoot = $ENV{SOURCE_ROOT}; my @directories = map { $_ = "$sourceRoot/$_" if $sourceRoot && -d "$sourceRoot/$_"; $_ } @$useDirectories; push(@directories, "."); $idlFiles = { }; my $wanted = sub { $idlFiles->{$1} = $File::Find::name if /^([A-Z].*)\.idl$/; $File::Find::prune = 1 if /^\../; }; find($wanted, @directories); } return $idlFiles->{$interfaceName}; } sub GetInterfaceForType { my ($object, $currentInterface, $type) = @_; return undef unless $object->IsInterfaceType($type); return $object->ParseInterface($currentInterface, $type->name); } sub GetAttributeFromInterface { my ($object, $outerInterface, $interfaceName, $attributeName) = @_; my $interface = $object->ParseInterface($outerInterface, $interfaceName); for my $attribute (@{$interface->attributes}) { return $attribute if $attribute->name eq $attributeName; } die("Could not find attribute '$attributeName' on interface '$interfaceName'."); } sub ParseInterface { my $object = shift; my $outerInterface = shift; my $interfaceName = shift; return undef if $interfaceName eq 'Object'; return undef if $interfaceName eq 'UNION'; if (exists $cachedInterfaces->{$interfaceName}) { return $cachedInterfaces->{$interfaceName}; } # Step #1: Find the IDL file associated with 'interface' my $filename = $object->IDLFileForInterface($interfaceName) or assert("Could NOT find IDL file for interface \"$interfaceName\", reachable from \"" . $outerInterface->type->name . "\"!\n"); print " | |> Parsing parent IDL \"$filename\" for interface \"$interfaceName\"\n" if $verbose; # Step #2: Parse the found IDL file (in quiet mode). my $parser = IDLParser->new(1); my $document = $parser->Parse($filename, $defines, $preprocessor, $idlAttributes); foreach my $interface (@{$document->interfaces}) { if ($interface->type->name eq $interfaceName) { $cachedInterfaces->{$interfaceName} = $interface; return $interface; } } die("Could NOT find interface definition for $interfaceName in $filename"); } sub ParseType { my ($object, $typeString) = @_; return $cachedTypes->{$typeString} if exists($cachedTypes->{$typeString}); my $parser = IDLParser->new(1); my $type = $parser->ParseType($typeString, $idlAttributes); $cachedTypes->{$typeString} = $type; return $type; } # Helpers for all CodeGenerator***.pm modules sub IsNumericType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return 1 if $integerTypeHash{$type->name}; return 1 if $floatingPointTypeHash{$type->name}; return 0; } sub IsStringOrEnumType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return 1 if $object->IsStringType($type); return 1 if $object->IsEnumType($type); return 0; } sub IsIntegerType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return 1 if $integerTypeHash{$type->name}; return 0; } sub IsFloatingPointType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return 1 if $floatingPointTypeHash{$type->name}; return 0; } sub IsPrimitiveType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return 1 if $primitiveTypeHash{$type->name}; return 1 if $object->IsNumericType($type); return 0; } sub IsStringType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return 1 if $stringTypeHash{$type->name}; return 0; } sub IsEnumType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return defined($object->GetEnumByType($type)); } sub GetEnumByType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; my $name = $type->name; die "GetEnumByType() was called with an undefined enumeration name" unless defined($name); for my $enumeration (@{$useDocument->enumerations}) { return $enumeration if $enumeration->type->name eq $name; } return $cachedExternalEnumerations->{$name} if exists($cachedExternalEnumerations->{$name}); # Find the IDL file associated with the dictionary. my $filename = $object->IDLFileForInterface($name) or return; # Do a fast check to see if it seems to contain a dictionary. my $fileContents = slurp($filename); if ($fileContents =~ /\benum\s+$name/gs) { # Parse the IDL. my $parser = IDLParser->new(1); my $document = $parser->Parse($filename, $defines, $preprocessor, $idlAttributes); foreach my $enumeration (@{$document->enumerations}) { next unless $enumeration->type->name eq $name; $cachedExternalEnumerations->{$name} = $enumeration; my $implementedAs = $enumeration->extendedAttributes->{ImplementedAs}; $enumTypeImplementationNameOverrides{$enumeration->type->name} = $implementedAs if $implementedAs; return $enumeration; } } $cachedExternalEnumerations->{$name} = undef; } # An enumeration defined in its own IDL file. sub IsExternalEnumType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return $object->IsEnumType($type) && defined($cachedExternalEnumerations->{$type->name}); } sub HasEnumImplementationNameOverride { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return 1 if exists $enumTypeImplementationNameOverrides{$type->name}; return 0; } sub GetEnumImplementationNameOverride { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return $enumTypeImplementationNameOverrides{$type->name}; } sub GetDictionaryByType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; my $name = $type->name; die "GetDictionaryByType() was called with an undefined dictionary name" unless defined($name); for my $dictionary (@{$useDocument->dictionaries}) { return $dictionary if $dictionary->type->name eq $name; } return $cachedExternalDictionaries->{$name} if exists($cachedExternalDictionaries->{$name}); # Find the IDL file associated with the dictionary. my $filename = $object->IDLFileForInterface($name) or return; # Do a fast check to see if it seems to contain a dictionary. my $fileContents = slurp($filename); if ($fileContents =~ /\bdictionary\s+$name/gs) { # Parse the IDL. my $parser = IDLParser->new(1); my $document = $parser->Parse($filename, $defines, $preprocessor, $idlAttributes); foreach my $dictionary (@{$document->dictionaries}) { if ($dictionary->type->name eq $name) { $object->ProcessDictionarySupplementalDependencies($dictionary, $document); $cachedExternalDictionaries->{$name} = $dictionary; my $implementedAs = $dictionary->extendedAttributes->{ImplementedAs}; $dictionaryTypeImplementationNameOverrides{$name} = $implementedAs if $implementedAs; return $dictionary; } } } $cachedExternalDictionaries->{$name} = undef; } sub IsDictionaryType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return $type->name =~ /^[A-Z]/ && defined($object->GetDictionaryByType($type)); } # A dictionary defined in its own IDL file. sub IsExternalDictionaryType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return $object->IsDictionaryType($type) && defined($cachedExternalDictionaries->{$type->name}); } sub HasDictionaryImplementationNameOverride { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return 1 if exists $dictionaryTypeImplementationNameOverrides{$type->name}; return 0; } sub GetDictionaryImplementationNameOverride { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return $dictionaryTypeImplementationNameOverrides{$type->name}; } sub IsSVGAnimatedTypeName { my ($object, $typeName) = @_; return $typeName =~ /^SVGAnimated/; } sub IsSVGAnimatedType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return $object->IsSVGAnimatedTypeName($type->name); } sub IsSVGPathSegTypeName { my ($object, $typeName) = @_; return $typeName =~ /^SVGPathSeg/; } sub IsSVGPathSegType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return $object->IsSVGPathSegTypeName($type->name); } sub IsConstructorType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return $type->name =~ /Constructor$/; } sub IsSequenceType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return $type->name eq "sequence"; } sub IsFrozenArrayType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return $type->name eq "FrozenArray"; } sub IsSequenceOrFrozenArrayType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return $object->IsSequenceType($type) || $object->IsFrozenArrayType($type); } sub IsRecordType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return $type->name eq "record"; } sub IsBufferSourceType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return 1 if $bufferSourceTypes{$type->name}; return 0; } sub IsPromiseType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return $type->name eq "Promise"; } # These match WK_lcfirst and WK_ucfirst defined in builtins_generator.py. # Uppercase the first letter while respecting WebKit style guidelines. # E.g., xmlEncoding becomes XMLEncoding, but xmlllang becomes Xmllang. sub WK_ucfirst { my ($object, $param) = @_; my $ret = ucfirst($param); $ret =~ s/Xml/XML/ if $ret =~ /^Xml[^a-z]/; $ret =~ s/Svg/SVG/ if $ret =~ /^Svg/; $ret =~ s/Srgb/SRGB/ if $ret =~ /^Srgb/; $ret =~ s/Cenc/cenc/ if $ret =~ /^Cenc/; $ret =~ s/Cbcs/cbcs/ if $ret =~ /^Cbcs/; $ret =~ s/Pq/PQ/ if $ret =~ /^Pq$/; $ret =~ s/Hlg/HLG/ if $ret =~ /^Hlg/; $ret =~ s/Ios/iOS/ if $ret =~ /^Ios/; return $ret; } # Lowercase the first letter while respecting WebKit style guidelines. # URL becomes url, but SetURL becomes setURL. sub WK_lcfirst { my ($object, $param) = @_; my $ret = lcfirst($param); $ret =~ s/dOM/dom/ if $ret =~ /^dOM/; $ret =~ s/hTML/html/ if $ret =~ /^hTML/; $ret =~ s/uRL/url/ if $ret =~ /^uRL/; $ret =~ s/jS/js/ if $ret =~ /^jS/; $ret =~ s/xML/xml/ if $ret =~ /^xML/; $ret =~ s/xSLT/xslt/ if $ret =~ /^xSLT/; $ret =~ s/cSS/css/ if $ret =~ /^cSS/; $ret =~ s/rTC/rtc/ if $ret =~ /^rTC/; # For HTML5 FileSystem API Flags attributes. # (create is widely used to instantiate an object and must be avoided.) $ret =~ s/^create/isCreate/ if $ret =~ /^create$/; $ret =~ s/^exclusive/isExclusive/ if $ret =~ /^exclusive$/; return $ret; } sub slurp { my $file = shift; open my $fh, '<', $file or die; local $/ = undef; my $content = <$fh>; close $fh; return $content; } sub trim { my $string = shift; $string =~ s/^\s+|\s+$//g; return $string; } # Return the C++ namespace that a given attribute name string is defined in. sub NamespaceForAttributeName { my ($object, $interfaceName, $attributeName) = @_; return "SVGNames" if $interfaceName =~ /^SVG/ && !$svgAttributesInHTMLHash{$attributeName}; return "HTMLNames"; } # Identifies overloaded functions and for each function adds an array with # links to its respective overloads (including itself). sub LinkOverloadedOperations { my ($object, $interface) = @_; my %nameToOperationsMap = (); foreach my $operation (@{$interface->operations}) { my $name = $operation->name; $nameToOperationsMap{$name} = [] if !exists $nameToOperationsMap{$name}; push(@{$nameToOperationsMap{$name}}, $operation); $operation->{overloads} = $nameToOperationsMap{$name}; $operation->{overloadIndex} = @{$nameToOperationsMap{$name}}; } my $index = 1; foreach my $constructor (@{$interface->constructors}) { $constructor->{overloads} = $interface->constructors; $constructor->{overloadIndex} = $index; $index++; } } sub AttributeNameForGetterAndSetter { my ($generator, $attribute) = @_; my $attributeName = $attribute->name; if ($attribute->extendedAttributes->{"ImplementedAs"}) { $attributeName = $attribute->extendedAttributes->{"ImplementedAs"}; } my $attributeType = $attribute->type; # SVG animated types need to use a special attribute name. # The rest of the special casing for SVG animated types is handled in the language-specific code generators. $attributeName .= "Animated" if $generator->IsSVGAnimatedType($attributeType); return $attributeName; } sub ContentAttributeName { my ($generator, $implIncludes, $interfaceName, $attribute) = @_; my $contentAttributeName = $attribute->extendedAttributes->{"Reflect"}; return undef if !$contentAttributeName; $contentAttributeName = lc $generator->AttributeNameForGetterAndSetter($attribute) if $contentAttributeName eq "VALUE_IS_MISSING"; my $namespace = $generator->NamespaceForAttributeName($interfaceName, $contentAttributeName); $implIncludes->{"${namespace}.h"} = 1; return "WebCore::${namespace}::${contentAttributeName}Attr"; } sub GetterExpression { my ($generator, $implIncludes, $interfaceName, $attribute) = @_; my $contentAttributeName = $generator->ContentAttributeName($implIncludes, $interfaceName, $attribute); if (!$contentAttributeName) { return ($generator->WK_lcfirst($generator->AttributeNameForGetterAndSetter($attribute))); } my $attributeType = $attribute->type; my $functionName; if ($attribute->extendedAttributes->{"URL"}) { $functionName = "getURLAttribute"; } elsif ($attributeType->name eq "boolean") { $functionName = "hasAttributeWithoutSynchronization"; } elsif ($attributeType->name eq "long") { $functionName = "getIntegralAttribute"; } elsif ($attributeType->name eq "unsigned long") { $functionName = "getUnsignedIntegralAttribute"; } else { if ($contentAttributeName eq "WebCore::HTMLNames::idAttr") { $functionName = "getIdAttribute"; $contentAttributeName = ""; } elsif ($contentAttributeName eq "WebCore::HTMLNames::nameAttr") { $functionName = "getNameAttribute"; $contentAttributeName = ""; } elsif ($generator->IsSVGAnimatedType($attributeType)) { $functionName = "getAttribute"; } else { $functionName = "attributeWithoutSynchronization"; } } return ($functionName, $contentAttributeName); } sub SetterExpression { my ($generator, $implIncludes, $interfaceName, $attribute) = @_; my $contentAttributeName = $generator->ContentAttributeName($implIncludes, $interfaceName, $attribute); if (!$contentAttributeName) { return ("set" . $generator->WK_ucfirst($generator->AttributeNameForGetterAndSetter($attribute))); } my $attributeType = $attribute->type; my $functionName; if ($attributeType->name eq "boolean") { $functionName = "setBooleanAttribute"; } elsif ($attributeType->name eq "long") { $functionName = "setIntegralAttribute"; } elsif ($attributeType->name eq "unsigned long") { $functionName = "setUnsignedIntegralAttribute"; } elsif ($generator->IsSVGAnimatedType($attributeType)) { $functionName = "setAttribute"; } else { $functionName = "setAttributeWithoutSynchronization"; } return ($functionName, $contentAttributeName); } sub IsBuiltinType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return 1 if $object->IsPrimitiveType($type); return 1 if $object->IsSequenceOrFrozenArrayType($type); return 1 if $object->IsRecordType($type); return 1 if $object->IsStringType($type); return 1 if $object->IsBufferSourceType($type); return 1 if $type->isUnion; return 1 if $type->name eq "EventListener"; return 1 if $type->name eq "JSON"; return 1 if $type->name eq "Promise"; return 1 if $type->name eq "ScheduledAction"; return 1 if $type->name eq "SerializedScriptValue"; return 1 if $type->name eq "any"; return 1 if $type->name eq "object"; return 0; } sub IsInterfaceType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return 0 if $object->IsBuiltinType($type); return 0 if $object->IsDictionaryType($type); return 0 if $object->IsEnumType($type); return 1; } sub IsWrapperType { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return 1 if $object->IsInterfaceType($type); return 0; } sub InterfaceHasRegularToJSONOperation { my ($object, $interface) = @_; foreach my $operation (@{$interface->operations}) { if (!$operation->isStatic && $operation->name eq "toJSON") { return 1; } } return 0; } # https://heycam.github.io/webidl/#dfn-json-types sub IsJSONType { my ($object, $interface, $type) = @_; return 1 if $type->name eq "object"; return 1 if $type->name eq "boolean"; return 1 if $object->IsNumericType($type); return 1 if $object->IsStringOrEnumType($type); if ($object->IsSequenceOrFrozenArrayType($type)) { my $subtype = @{$type->subtypes}[0]; return $object->IsJSONType($interface, $subtype); } if ($object->IsRecordType($type)) { my $valueSubtype = @{$type->subtypes}[0]; return $object->IsJSONType($interface, $valueSubtype); } if ($object->IsInterfaceType($type)) { # Special case EventHandler, since there is no real IDL for it. return 0 if $type->name eq "EventHandler"; my $interface = $object->GetInterfaceForType($interface, $type); if ($object->InterfaceHasRegularToJSONOperation($interface)) { return 1; } my $anyParentHasRegularToJSONOperation = 0; $object->ForAllParents($interface, sub { my $parentInterface = shift; $anyParentHasRegularToJSONOperation = 1 if $object->InterfaceHasRegularToJSONOperation($parentInterface); }, 0); if ($anyParentHasRegularToJSONOperation) { return 1; } } if ($type->isUnion) { # FIXME: Union types should be supported if all the member types are JSON types. die "Default toJSON is currently not supported for union types.\n"; } if ($object->IsDictionaryType($type)) { # FIXME: Dictionary types should be supported if all members declared on the dictionary and all its inherited dictionaries are JSON types. die "Default toJSON is currently not supported for dictionary types.\n"; } return 0; } sub hasCachedAttributeOrCustomGetterExtendedAttribute { my ($attribute) = @_; return $attribute->extendedAttributes->{CachedAttribute} || $attribute->extendedAttributes->{CustomGetter}; } sub GetInterfaceExtendedAttributesFromName { # FIXME: It's bad to have a function like this that opens another IDL file to answer a question. # Overusing this kind of function can make things really slow. Lets avoid these if we can. my ($object, $interfaceName) = @_; my $idlFile = $object->IDLFileForInterface($interfaceName) or assert("Could NOT find IDL file for interface \"$interfaceName\"!\n"); open FILE, "<", $idlFile or die; my @lines = ; close FILE; my $fileContents = join('', @lines); my $extendedAttributes = {}; if ($fileContents =~ /\[(.*)\]\s+(callback interface|interface)\s+(\w+)/gs) { my @parts = split(',', $1); 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; } } return $extendedAttributes; } sub ComputeIsCallbackInterface { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return 0 unless $object->IsInterfaceType($type); return 0 if $type->name eq "WindowProxy"; my $typeName = $type->name; my $idlFile = $object->IDLFileForInterface($typeName) or assert("Could NOT find IDL file for interface \"$typeName\"!\n"); open FILE, "<", $idlFile or die; my @lines = ; close FILE; my $fileContents = join('', @lines); return ($fileContents =~ /callback\s+interface\s+(\w+)/gs); } my %isCallbackInterface = (); sub IsCallbackInterface { # FIXME: It's bad to have a function like this that opens another IDL file to answer a question. # Overusing this kind of function can make things really slow. Lets avoid these if we can. # To mitigate that, lets cache what we learn in a hash so we don't open the same file over and over. my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return $isCallbackInterface{$type->name} if exists $isCallbackInterface{$type->name}; my $result = $object->ComputeIsCallbackInterface($type); $isCallbackInterface{$type->name} = $result; return $result; } sub ComputeIsCallbackFunction { my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return 0 unless $object->IsInterfaceType($type); return 0 if $type->name eq "WindowProxy"; my $typeName = $type->name; my $idlFile = $object->IDLFileForInterface($typeName) or assert("Could NOT find IDL file for interface \"$typeName\"!\n"); open FILE, "<", $idlFile or die; my @lines = ; close FILE; my $fileContents = join('', @lines); return ($fileContents =~ /(.*)callback\s+(\w+)\s+=/gs); } my %isCallbackFunction = (); sub IsCallbackFunction { # FIXME: It's bad to have a function like this that opens another IDL file to answer a question. # Overusing this kind of function can make things really slow. Lets avoid these if we can. # To mitigate that, lets cache what we learn in a hash so we don't open the same file over and over. my ($object, $type) = @_; assert("Not a type") if ref($type) ne "IDLType"; return $isCallbackFunction{$type->name} if exists $isCallbackFunction{$type->name}; my $result = $object->ComputeIsCallbackFunction($type); $isCallbackFunction{$type->name} = $result; return $result; } sub GenerateConditionalString { my ($generator, $node) = @_; my $conditional = $node->extendedAttributes->{"Conditional"}; if ($conditional) { return $generator->GenerateConditionalStringFromAttributeValue($conditional); } else { return ""; } } sub GenerateConditionalStringFromAttributeValue { my ($generator, $conditional) = @_; my %disjunction; map { my $expression = $_; my %conjunction; map { $conjunction{$_} = 1; } split(/&/, $expression); $expression = "ENABLE(" . join(") && ENABLE(", sort keys %conjunction) . ")"; $disjunction{$expression} = 1 } split(/\|/, $conditional); return "1" if keys %disjunction == 0; return (%disjunction)[0] if keys %disjunction == 1; my @parenthesized; map { my $expression = $_; $expression = "($expression)" if $expression =~ / /; push @parenthesized, $expression; } sort keys %disjunction; return join(" || ", @parenthesized); } sub GenerateCompileTimeCheckForEnumsIfNeeded { my ($generator, $interface) = @_; return () if $interface->extendedAttributes->{"DoNotCheckConstants"} || !@{$interface->constants}; my $baseScope = $interface->extendedAttributes->{"ConstantsScope"} || $interface->type->name; my @checks = (); foreach my $constant (@{$interface->constants}) { my $scope = $constant->extendedAttributes->{"ImplementedBy"} || $baseScope; my $name = $constant->extendedAttributes->{"Reflect"} || $constant->name; my $value = $constant->value; my $conditional = $constant->extendedAttributes->{"Conditional"}; push(@checks, "#if " . $generator->GenerateConditionalStringFromAttributeValue($conditional) . "\n") if $conditional; push(@checks, "static_assert(${scope}::${name} == ${value}, \"${name} in ${scope} does not match value from IDL\");\n"); push(@checks, "#endif\n") if $conditional; } push(@checks, "\n"); return @checks; } sub ExtendedAttributeContains { my $object = shift; my $extendedAttribute = shift; return 0 unless $extendedAttribute; my $keyword = shift; my @extendedAttributeKeywords = split /\s*\&\s*/, $extendedAttribute; return grep { $_ eq $keyword } @extendedAttributeKeywords; } # FIXME: This is backwards. We currently name the interface and the IDL files with the implementation name. We # should use the real interface name in the IDL files and then use ImplementedAs to map this to the implementation name. sub GetVisibleInterfaceName { my ($object, $interface) = @_; my $interfaceName = $interface->extendedAttributes->{"InterfaceName"}; return $interfaceName ? $interfaceName : $interface->type->name; } sub InheritsInterface { my ($object, $interface, $interfaceName) = @_; return 1 if $interfaceName eq $interface->type->name; my $found = 0; $object->ForAllParents($interface, sub { my $currentInterface = shift; if ($currentInterface->type->name eq $interfaceName) { $found = 1; } return 1 if $found; }, 0); return $found; } sub InheritsExtendedAttribute { my ($object, $interface, $extendedAttribute) = @_; return 1 if $interface->extendedAttributes->{$extendedAttribute}; my $found = 0; $object->ForAllParents($interface, sub { my $currentInterface = shift; if ($currentInterface->extendedAttributes->{$extendedAttribute}) { $found = 1; } return 1 if $found; }, 0); return $found; } 1;