haikuwebkit/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm

8111 lines
376 KiB
Perl
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#
# Copyright (C) 2005, 2006, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
# Copyright (C) 2006 Anders Carlsson <andersca@mac.com>
# Copyright (C) 2006, 2007 Samuel Weinig <sam@webkit.org>
# Copyright (C) 2006 Alexey Proskuryakov <ap@webkit.org>
# Copyright (C) 2006-2021 Apple Inc. All rights reserved.
# Copyright (C) 2009 Cameron McCormack <cam@mcc.id.au>
# Copyright (C) Research In Motion Limited 2010. All rights reserved.
# Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
# Copyright (C) 2011 Patrick Gansterer <paroga@webkit.org>
# Copyright (C) 2012 Ericsson AB. All rights reserved.
# Copyright (C) 2007, 2008, 2009, 2012 Google Inc.
# Copyright (C) 2013 Samsung Electronics. All rights reserved.
# Copyright (C) 2015, 2016 Canon 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.
package CodeGeneratorJS;
use strict;
use constant FileNamePrefix => "JS";
use Carp qw<longmess>;
use Data::Dumper;
use Hasher;
my $codeGenerator;
my $writeDependencies;
my @headerContentHeader = ();
my @headerContent = ();
my %headerIncludes = ();
my %headerTrailingIncludes = ();
my @implContentHeader = ();
my @implContent = ();
my %implIncludes = ();
my @depsContent = ();
my $numCachedAttributes = 0;
my $beginAppleCopyrightForHeaderFiles = <<END;
// ------- Begin Apple Copyright -------
/*
* Copyright (C) 2008 Apple Inc. All rights reserved.
*
* Permission is granted by Apple to use this file to the extent
* necessary to relink with LGPL WebKit files.
*
* No license or rights are granted by Apple expressly or by
* implication, estoppel, or otherwise, to Apple patents and
* trademarks. For the sake of clarity, no license or rights are
* granted by Apple expressly or by implication, estoppel, or otherwise,
* under any Apple patents, copyrights and trademarks to underlying
* implementations of any application programming interfaces (APIs)
* or to any functionality that is invoked by calling any API.
*/
END
my $beginAppleCopyrightForSourceFiles = <<END;
// ------- Begin Apple Copyright -------
/*
* Copyright (C) 2008 Apple Inc. All rights reserved.
*
* No license or rights are granted by Apple expressly or by implication,
* estoppel, or otherwise, to Apple copyrights, patents, trademarks, trade
* secrets or other rights.
*/
END
my $endAppleCopyright = <<END;
// ------- End Apple Copyright -------
END
# Default .h template
my $headerTemplate = << "EOF";
/*
This file is part of the WebKit open source project.
This file has been generated by generate-bindings.pl. DO NOT MODIFY!
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.
*/
EOF
sub assert
{
my $message = shift;
my $mess = longmess();
print Dumper($mess);
die $message;
}
# Default constructor
sub new
{
my $object = shift;
my $reference = { };
$codeGenerator = shift;
$writeDependencies = shift;
bless($reference, $object);
return $reference;
}
sub GenerateEnumeration
{
my ($object, $enumeration) = @_;
my $className = GetEnumerationClassName($enumeration->type);
$object->GenerateEnumerationHeader($enumeration, $className);
$object->GenerateEnumerationImplementation($enumeration, $className);
}
sub GenerateDictionary
{
my ($object, $dictionary, $enumerations, $otherDictionaries) = @_;
my $className = GetDictionaryClassName($dictionary->type);
$object->GenerateDictionaryHeader($dictionary, $className, $enumerations, $otherDictionaries);
$object->GenerateDictionaryImplementation($dictionary, $className, $enumerations, $otherDictionaries);
}
sub GenerateCallbackFunction
{
my ($object, $callbackFunction, $enumerations, $dictionaries) = @_;
$object->GenerateCallbackFunctionHeader($callbackFunction, $enumerations, $dictionaries);
$object->GenerateCallbackFunctionImplementation($callbackFunction, $enumerations, $dictionaries);
}
sub GenerateInterface
{
my ($object, $interface, $defines, $enumerations, $dictionaries) = @_;
$codeGenerator->LinkOverloadedOperations($interface);
AddIterableOperationIfNeeded($interface);
AddMapLikeAttributesAndOperationIfNeeded($interface);
AddSetLikeAttributesAndOperationIfNeeded($interface);
AddStringifierOperationIfNeeded($interface);
AddSharedSyntheticAttributesIfNeeded($interface);
if ($interface->isCallback) {
$object->GenerateCallbackInterfaceHeader($interface, $enumerations, $dictionaries);
$object->GenerateCallbackInterfaceImplementation($interface, $enumerations, $dictionaries);
} else {
$object->GenerateHeader($interface, $enumerations, $dictionaries);
$object->GenerateImplementation($interface, $enumerations, $dictionaries);
}
}
sub AddIterableOperationIfNeeded
{
my $interface = shift;
return unless $interface->iterable;
$interface->iterable->extendedAttributes->{FromIterable} = 1;
my $symbolIteratorOperation = IDLOperation->new();
$symbolIteratorOperation->name("[Symbol.Iterator]");
IDLParser::copyExtendedAttributes($symbolIteratorOperation->extendedAttributes, $interface->iterable->extendedAttributes);
push(@{$interface->iterable->operations}, $symbolIteratorOperation);
push(@{$interface->operations}, $symbolIteratorOperation) if IsKeyValueIterableInterface($interface);
my $entriesOperation = IDLOperation->new();
$entriesOperation->name("entries");
IDLParser::copyExtendedAttributes($entriesOperation->extendedAttributes, $interface->iterable->extendedAttributes);
push(@{$interface->iterable->operations}, $entriesOperation);
push(@{$interface->operations}, $entriesOperation) if IsKeyValueIterableInterface($interface);
my $keysOperation = IDLOperation->new();
$keysOperation->name("keys");
IDLParser::copyExtendedAttributes($keysOperation->extendedAttributes, $interface->iterable->extendedAttributes);
push(@{$interface->iterable->operations}, $keysOperation);
push(@{$interface->operations}, $keysOperation) if IsKeyValueIterableInterface($interface);
my $valuesOperation = IDLOperation->new();
$valuesOperation->name("values");
IDLParser::copyExtendedAttributes($valuesOperation->extendedAttributes, $interface->iterable->extendedAttributes);
push(@{$interface->iterable->operations}, $valuesOperation);
push(@{$interface->operations}, $valuesOperation) if IsKeyValueIterableInterface($interface);
my $forEachOperation = IDLOperation->new();
$forEachOperation->name("forEach");
my $forEachArgument = IDLArgument->new();
$forEachArgument->name("callback");
$forEachArgument->type(IDLParser::makeSimpleType("any"));
push(@{$forEachOperation->arguments}, ($forEachArgument));
IDLParser::copyExtendedAttributes($forEachOperation->extendedAttributes, $interface->iterable->extendedAttributes);
push(@{$interface->iterable->operations}, $forEachOperation);
push(@{$interface->operations}, $forEachOperation) if IsKeyValueIterableInterface($interface);
}
sub AddMapLikeAttributesAndOperationIfNeeded
{
my $interface = shift;
return unless $interface->mapLike;
$interface->mapLike->extendedAttributes->{ForwardToMapLike} = 1;
# https://heycam.github.io/webidl/#es-map-size
my $sizeAttribute = IDLAttribute->new();
$sizeAttribute->name("size");
$sizeAttribute->isReadOnly(1);
$sizeAttribute->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($sizeAttribute->extendedAttributes, $interface->mapLike->extendedAttributes);
$sizeAttribute->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->attributes}, $sizeAttribute);
# https://heycam.github.io/webidl/#es-map-get-has
my $getOperation = IDLOperation->new();
$getOperation->name("get");
my $getArgument = IDLArgument->new();
$getArgument->name("key");
$getArgument->type(IDLParser::cloneType($interface->mapLike->keyType));
push(@{$getOperation->arguments}, ($getArgument));
$getOperation->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($getOperation->extendedAttributes, $interface->mapLike->extendedAttributes);
$getOperation->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->operations}, $getOperation);
my $hasOperation = IDLOperation->new();
$hasOperation->name("has");
my $hasArgument = IDLArgument->new();
$hasArgument->name("key");
$hasArgument->type(IDLParser::cloneType($interface->mapLike->keyType));
push(@{$hasOperation->arguments}, ($hasArgument));
$hasOperation->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($hasOperation->extendedAttributes, $interface->mapLike->extendedAttributes);
$hasOperation->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->operations}, $hasOperation);
# https://heycam.github.io/webidl/#es-map-entries
my $entriesOperation = IDLOperation->new();
$entriesOperation->name("entries");
$entriesOperation->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($entriesOperation->extendedAttributes, $interface->mapLike->extendedAttributes);
$entriesOperation->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->operations}, $entriesOperation);
# https://heycam.github.io/webidl/#es-map-keys-values
my $keysOperation = IDLOperation->new();
$keysOperation->name("keys");
$keysOperation->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($keysOperation->extendedAttributes, $interface->mapLike->extendedAttributes);
$keysOperation->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->operations}, $keysOperation);
my $valuesOperation = IDLOperation->new();
$valuesOperation->name("values");
$valuesOperation->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($valuesOperation->extendedAttributes, $interface->mapLike->extendedAttributes);
$valuesOperation->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->operations}, $valuesOperation);
# https://heycam.github.io/webidl/#es-forEach
my $forEachOperation = IDLOperation->new();
$forEachOperation->name("forEach");
$forEachOperation->type(IDLParser::makeSimpleType("any"));
my $forEachArgument = IDLArgument->new();
$forEachArgument->name("callback");
$forEachArgument->type(IDLParser::makeSimpleType("any"));
push(@{$forEachOperation->arguments}, ($forEachArgument));
IDLParser::copyExtendedAttributes($forEachOperation->extendedAttributes, $interface->mapLike->extendedAttributes);
push(@{$interface->operations}, $forEachOperation);
return if $interface->mapLike->isReadOnly;
# https://heycam.github.io/webidl/#es-map-set
unless (grep { $_->name eq "set" } (@{$interface->attributes}, @{$interface->operations}, @{$interface->constants})) {
my $setOperation = IDLOperation->new();
$setOperation->name("set");
my $setKeyArgument = IDLArgument->new();
$setKeyArgument->name("key");
$setKeyArgument->type(IDLParser::cloneType($interface->mapLike->keyType));
my $setValueArgument = IDLArgument->new();
$setValueArgument->name("value");
$setValueArgument->type(IDLParser::cloneType($interface->mapLike->valueType));
push(@{$setOperation->arguments}, ($setKeyArgument));
push(@{$setOperation->arguments}, ($setValueArgument));
$setOperation->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($setOperation->extendedAttributes, $interface->mapLike->extendedAttributes);
$setOperation->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->operations}, $setOperation);
}
# https://heycam.github.io/webidl/#es-map-clear
unless (grep { $_->name eq "clear" } (@{$interface->attributes}, @{$interface->operations}, @{$interface->constants})) {
my $clearOperation = IDLOperation->new();
$clearOperation->name("clear");
$clearOperation->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($clearOperation->extendedAttributes, $interface->mapLike->extendedAttributes);
$clearOperation->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->operations}, $clearOperation);
}
# https://heycam.github.io/webidl/#es-map-delete
unless (grep { $_->name eq "delete" } (@{$interface->attributes}, @{$interface->operations}, @{$interface->constants})) {
my $deleteOperation = IDLOperation->new();
$deleteOperation->name("delete");
my $deleteArgument = IDLArgument->new();
$deleteArgument->name("key");
$deleteArgument->type(IDLParser::cloneType($interface->mapLike->keyType));
push(@{$deleteOperation->arguments}, ($deleteArgument));
$deleteOperation->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($deleteOperation->extendedAttributes, $interface->mapLike->extendedAttributes);
$deleteOperation->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->operations}, $deleteOperation);
}
}
sub AddSetLikeAttributesAndOperationIfNeeded
{
my $interface = shift;
return unless $interface->setLike;
$interface->setLike->extendedAttributes->{ForwardToSetLike} = 1;
# https://heycam.github.io/webidl/#es-set-size
my $sizeAttribute = IDLAttribute->new();
$sizeAttribute->name("size");
$sizeAttribute->isReadOnly(1);
$sizeAttribute->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($sizeAttribute->extendedAttributes, $interface->setLike->extendedAttributes);
$sizeAttribute->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->attributes}, $sizeAttribute);
# https://heycam.github.io/webidl/#es-set-has
my $hasOperation = IDLOperation->new();
$hasOperation->name("has");
my $hasArgument = IDLArgument->new();
$hasArgument->name("key");
$hasArgument->type(IDLParser::cloneType($interface->setLike->itemType));
push(@{$hasOperation->arguments}, ($hasArgument));
$hasOperation->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($hasOperation->extendedAttributes, $interface->setLike->extendedAttributes);
$hasOperation->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->operations}, $hasOperation);
# https://heycam.github.io/webidl/#es-set-entries-keys
my $entriesOperation = IDLOperation->new();
$entriesOperation->name("entries");
$entriesOperation->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($entriesOperation->extendedAttributes, $interface->setLike->extendedAttributes);
$entriesOperation->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->operations}, $entriesOperation);
my $keysOperation = IDLOperation->new();
$keysOperation->name("keys");
$keysOperation->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($keysOperation->extendedAttributes, $interface->setLike->extendedAttributes);
$keysOperation->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->operations}, $keysOperation);
# https://heycam.github.io/webidl/#es-set-values
my $valuesOperation = IDLOperation->new();
$valuesOperation->name("values");
$valuesOperation->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($valuesOperation->extendedAttributes, $interface->setLike->extendedAttributes);
$valuesOperation->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->operations}, $valuesOperation);
# https://heycam.github.io/webidl/#es-forEach
my $forEachOperation = IDLOperation->new();
$forEachOperation->name("forEach");
my $forEachArgument = IDLArgument->new();
$forEachArgument->name("callback");
$forEachArgument->type(IDLParser::makeSimpleType("any"));
push(@{$forEachOperation->arguments}, ($forEachArgument));
$forEachOperation->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($forEachOperation->extendedAttributes, $interface->setLike->extendedAttributes);
# FIXME: The WebIDL spec says that the forEach operation should be enumerable.
$forEachOperation->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->operations}, $forEachOperation);
return if $interface->setLike->isReadOnly;
# https://heycam.github.io/webidl/#es-add-delete
unless (grep { $_->name eq "add" } (@{$interface->attributes}, @{$interface->operations}, @{$interface->constants})) {
my $addOperation = IDLOperation->new();
$addOperation->name("add");
my $addArgument = IDLArgument->new();
$addArgument->name("key");
$addArgument->type(IDLParser::cloneType($interface->setLike->itemType));
push(@{$addOperation->arguments}, ($addArgument));
$addOperation->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($addOperation->extendedAttributes, $interface->setLike->extendedAttributes);
$addOperation->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->operations}, $addOperation);
}
# https://heycam.github.io/webidl/#es-set-clear
unless (grep { $_->name eq "clear" } (@{$interface->attributes}, @{$interface->operations}, @{$interface->constants})) {
my $clearOperation = IDLOperation->new();
$clearOperation->name("clear");
$clearOperation->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($clearOperation->extendedAttributes, $interface->setLike->extendedAttributes);
$clearOperation->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->operations}, $clearOperation);
}
unless (grep { $_->name eq "delete" } (@{$interface->attributes}, @{$interface->operations}, @{$interface->constants})) {
my $deleteOperation = IDLOperation->new();
$deleteOperation->name("delete");
my $deleteArgument = IDLArgument->new();
$deleteArgument->name("key");
$deleteArgument->type(IDLParser::cloneType($interface->setLike->itemType));
push(@{$deleteOperation->arguments}, ($deleteArgument));
$deleteOperation->type(IDLParser::makeSimpleType("any"));
IDLParser::copyExtendedAttributes($deleteOperation->extendedAttributes, $interface->setLike->extendedAttributes);
$deleteOperation->extendedAttributes->{NotEnumerable} = 1;
push(@{$interface->operations}, $deleteOperation);
}
}
sub AddStringifierOperationIfNeeded
{
my $interface = shift;
foreach my $property (@{$interface->attributes}, @{$interface->operations}, @{$interface->anonymousOperations}) {
next unless $property->isStringifier;
if (ref($property) eq "IDLAttribute") {
assert("stringifier can only be used on attributes with type DOMString or USVString") unless $property->type->name eq "DOMString" || $property->type->name eq "USVString";
}
if (ref($property) eq "IDLOperation") {
assert("stringifier can only be used on operations with a return type of DOMString") unless $property->type->name eq "DOMString";
assert("stringifier can only be used on operations with zero arguments") unless scalar(@{$property->arguments}) == 0;
# Don't duplicate the operation if it was declared with the name 'toString'.
return if $property->name eq "toString";
}
my $stringifier = IDLOperation->new();
$stringifier->name("toString");
$stringifier->type(IDLParser::cloneType($property->type));
$stringifier->isStringifier(1);
IDLParser::copyExtendedAttributes($stringifier->extendedAttributes, $property->extendedAttributes);
if ($property->name && !$stringifier->extendedAttributes->{ImplementedAs}) {
$stringifier->extendedAttributes->{ImplementedAs} = $property->name;
}
# If the stringifier was declared as read-write attribute and had [CEReactions], we need to remove
# it from the operation, as the operation should act like attribute getter, which doesn't respect
# [CEReactions].
if (ref($property) eq "IDLAttribute" && !$property->isReadOnly && $stringifier->extendedAttributes->{CEReactions}) {
delete $stringifier->extendedAttributes->{CEReactions};
}
push(@{$interface->operations}, $stringifier);
return;
}
}
sub EventHandlerAttributeEventName
{
my $attribute = shift;
my $eventType = $attribute->extendedAttributes->{ImplementedAs} || $attribute->name;
# Remove the "on" prefix.
$eventType = substr($eventType, 2);
return "eventNames().${eventType}Event";
}
sub GetParentClassName
{
my $interface = shift;
return $interface->extendedAttributes->{JSLegacyParent} if $interface->extendedAttributes->{JSLegacyParent};
return "JSDOMObject" unless NeedsImplementationClass($interface);
return "JSDOMWrapper<" . GetImplClassName($interface) . ">" unless $interface->parentType;
return "JS" . $interface->parentType->name;
}
sub GetCallbackClassName
{
my $className = shift;
return "JS$className";
}
sub GetExportMacroForJSClass
{
my $interface = shift;
return $interface->extendedAttributes->{ExportMacro} . " " if $interface->extendedAttributes->{ExportMacro};
return "";
}
sub AddIncludesForImplementationTypeInImpl
{
my $implementationType = shift;
AddIncludesForImplementationType($implementationType, \%implIncludes);
}
sub AddIncludesForImplementationTypeInHeader
{
my $implementationType = shift;
AddIncludesForImplementationType($implementationType, \%headerIncludes);
}
sub AddIncludesForImplementationType
{
my ($implementationType, $includesRef) = @_;
$includesRef->{"${implementationType}.h"} = 1;
}
sub AddToImplIncludesForIDLType
{
my ($type, $conditional) = @_;
return AddToIncludesForIDLType($type, \%implIncludes, $conditional)
}
sub AddToIncludesForIDLType
{
my ($type, $includesRef, $conditional) = @_;
if ($type->isNullable) {
AddToIncludes("JSDOMConvertNullable.h", $includesRef, $conditional);
}
if ($type->extendedAttributes->{OverrideIDLType}) {
my $overrideTypeName = $type->extendedAttributes->{OverrideIDLType};
if ($overrideTypeName eq "IDLIDBKey") {
AddToIncludes("JSDOMConvertIndexedDB.h", $includesRef, $conditional);
return;
}
if ($overrideTypeName eq "IDLWebGLAny" || $overrideTypeName eq "IDLWebGLExtension") {
AddToIncludes("JSDOMConvertWebGL.h", $includesRef, $conditional);
return;
}
}
if ($type->name eq "undefined") {
AddToIncludes("IDLTypes.h", $includesRef, $conditional);
AddToIncludes("JSDOMConvertBase.h", $includesRef, $conditional);
return;
}
if ($type->name eq "any") {
AddToIncludes("JSDOMConvertAny.h", $includesRef, $conditional);
return;
}
if ($type->name eq "boolean") {
AddToIncludes("JSDOMConvertBoolean.h", $includesRef, $conditional);
return;
}
if ($codeGenerator->IsBufferSourceType($type)) {
AddToIncludes("JSDOMConvertBufferSource.h", $includesRef, $conditional);
return;
}
if ($codeGenerator->IsCallbackFunction($type) || $codeGenerator->IsCallbackInterface($type)) {
AddToIncludes("JS" . $type->name . ".h", $includesRef, $conditional);
AddToIncludes("JSDOMConvertCallbacks.h", $includesRef, $conditional);
return;
}
if ($type->name eq "Date") {
AddToIncludes("JSDOMConvertDate.h", $includesRef, $conditional);
return;
}
if ($codeGenerator->IsExternalDictionaryType($type)) {
AddToIncludes("JS" . $type->name . ".h", $includesRef, $conditional);
AddToIncludes("JSDOMConvertDictionary.h", $includesRef, $conditional);
return;
}
if ($codeGenerator->IsExternalEnumType($type)) {
AddToIncludes("JS" . $type->name . ".h", $includesRef, $conditional);
AddToIncludes("JSDOMConvertEnumeration.h", $includesRef, $conditional);
return;
}
if ($type->name eq "EventListener") {
AddToIncludes("JSEventListener.h", $includesRef, $conditional);
AddToIncludes("JSDOMConvertEventListener.h", $includesRef, $conditional);
return;
}
if ($type->name eq "ReadableStream") {
AddToIncludes("ReadableStream.h", $includesRef, $conditional);
return;
}
if ($type->name eq "WritableStream") {
AddToIncludes("WritableStream.h", $includesRef, $conditional);
return;
}
if ($type->name eq "XPathNSResolver") {
AddToIncludes("JSXPathNSResolver.h", $includesRef, $conditional);
AddToIncludes("JSDOMConvertXPathNSResolver.h", $includesRef, $conditional);
return;
}
if ($codeGenerator->IsInterfaceType($type)) {
AddToIncludes("JS" . $type->name . ".h", $includesRef, $conditional);
AddToIncludes("JSDOMConvertInterface.h", $includesRef, $conditional);
return;
}
if ($type->name eq "JSON") {
AddToIncludes("JSDOMConvertJSON.h", $includesRef, $conditional);
return;
}
if ($codeGenerator->IsNumericType($type)) {
AddToIncludes("JSDOMConvertNumbers.h", $includesRef, $conditional);
return;
}
if ($type->name eq "object") {
AddToIncludes("JSDOMConvertObject.h", $includesRef, $conditional);
return;
}
if ($codeGenerator->IsPromiseType($type)) {
AddToIncludes("DOMPromiseProxy.h", $includesRef, $conditional);
AddToIncludes("JSDOMConvertPromise.h", $includesRef, $conditional);
AddToIncludesForIDLType(@{$type->subtypes}[0], $includesRef, $conditional);
return;
}
if ($codeGenerator->IsRecordType($type)) {
AddToIncludes("<wtf/Vector.h>", $includesRef, $conditional);
AddToIncludes("JSDOMConvertRecord.h", $includesRef, $conditional);
AddToIncludesForIDLType(@{$type->subtypes}[0], $includesRef, $conditional);
AddToIncludesForIDLType(@{$type->subtypes}[1], $includesRef, $conditional);
return;
}
if ($codeGenerator->IsSequenceOrFrozenArrayType($type)) {
AddToIncludes("<JavaScriptCore/JSArray.h>", $includesRef, $conditional);
AddToIncludes("JSDOMConvertSequences.h", $includesRef, $conditional);
AddToIncludesForIDLType(@{$type->subtypes}[0], $includesRef, $conditional);
return;
}
if ($type->name eq "ScheduledAction") {
AddToIncludes("JSDOMConvertScheduledAction.h", $includesRef, $conditional);
return;
}
if ($type->name eq "SerializedScriptValue") {
AddToIncludes("SerializedScriptValue.h", $includesRef, $conditional);
AddToIncludes("JSDOMConvertSerializedScriptValue.h", $includesRef, $conditional);
return;
}
if ($codeGenerator->IsStringType($type)) {
AddToIncludes("JSDOMConvertStrings.h", $includesRef, $conditional);
return;
}
if ($type->isUnion) {
AddToIncludes("<wtf/Variant.h>", $includesRef, $conditional);
AddToIncludes("JSDOMConvertUnion.h", $includesRef, $conditional);
foreach my $memberType (@{$type->subtypes}) {
AddToIncludesForIDLType($memberType, $includesRef, $conditional);
}
return;
}
}
sub AddToImplIncludes
{
my ($header, $conditional) = @_;
AddToIncludes($header, \%implIncludes, $conditional);
}
sub AddToIncludes
{
my ($header, $includesRef, $conditional) = @_;
if (not $conditional) {
$includesRef->{$header} = 1;
} elsif (not exists($includesRef->{$header})) {
$includesRef->{$header} = $conditional;
} else {
my $oldValue = $includesRef->{$header};
$includesRef->{$header} = "$oldValue|$conditional" if $oldValue ne 1;
}
}
sub IsReadonly
{
my $attribute = shift;
return $attribute->isReadOnly && !$attribute->extendedAttributes->{Replaceable} && !$attribute->extendedAttributes->{PutForwards};
}
sub AddClassForwardIfNeeded
{
my $type = shift;
# SVGAnimatedLength/Number/etc. are not classes so they can't be forward declared as classes.
return if $codeGenerator->IsSVGAnimatedType($type);
return if $codeGenerator->IsBufferSourceType($type);
push(@headerContent, "class " . $type->name . ";\n\n");
}
sub GetGenerateIsReachable
{
my $interface = shift;
return $interface->extendedAttributes->{GenerateIsReachable};
}
sub GetCustomIsReachable
{
my $interface = shift;
return $interface->extendedAttributes->{CustomIsReachable};
}
sub IsDOMGlobalObject
{
my $interface = shift;
return $interface->type->name eq "DOMWindow" || $interface->type->name eq "RemoteDOMWindow" || $codeGenerator->InheritsInterface($interface, "WorkerGlobalScope") || $codeGenerator->InheritsInterface($interface, "WorkletGlobalScope") || $interface->type->name eq "TestGlobalObject";
}
sub ShouldUseGlobalObjectPrototype
{
my $interface = shift;
# For workers, the global object is a DedicatedWorkerGlobalScope.
return 0 if $interface->type->name eq "WorkerGlobalScope";
# For worklets, the global object is a PaintWorkletGlobalScope or a AudioWorkletGlobalScope.
return 0 if $interface->type->name eq "WorkletGlobalScope";
return IsDOMGlobalObject($interface);
}
sub GenerateIndexedGetter
{
my ($interface, $indexedGetterOperation, $indexExpression) = @_;
# NOTE: This abstractly implements steps 1.2.1 - 1.2.8 of the LegacyPlatformObjectGetOwnProperty
# algorithm. Returing the conversion expression and attributes expression for use
# by the caller.
# 1.2.1 Let operation be the operation used to declare the indexed property getter.
# 1.2.2 Let value be an uninitialized variable.
# 1.2.3 If operation was defined without an identifier, then set value to the result
# of performing the steps listed in the interface description to determine the
# value of an indexed property with index as the index.
# 1.2.4 Otherwise, operation was defined with an identifier. Set value to the result
# of performing the steps listed in the description of operation with index as
# the only argument value.
# 1.2.5 Let desc be a newly created Property Descriptor with no fields.
# 1.2.6 Set desc.[[Value]] to the result of converting value to an ECMAScript value.
# 1.2.7 If O implements an interface with an indexed property setter, then set
# desc.[[Writable]] to true, otherwise set it to false.
# 1.2.8 Return desc.
my @attributes = ();
push(@attributes, "JSC::PropertyAttribute::ReadOnly") if !GetIndexedSetterOperation($interface) && !$interface->extendedAttributes->{Plugin};
my $attributeString = "static_cast<unsigned>(" . ((@attributes > 0) ? join(" | ", @attributes) : "0") . ")";
my $indexedGetterFunctionName = $indexedGetterOperation->extendedAttributes->{ImplementedAs} || $indexedGetterOperation->name || "item";
my $nativeToJSConversion = NativeToJSValueUsingPointers($indexedGetterOperation, $interface, "thisObject->wrapped().${indexedGetterFunctionName}(${indexExpression})", "*thisObject->globalObject()");
return ($nativeToJSConversion, $attributeString);
}
sub GenerateNamedGetter
{
my ($interface, $namedGetterOperation, $namedPropertyExpression) = @_;
# NOTE: This abstractly implements steps 2.1 - 2.10 of the LegacyPlatformObjectGetOwnProperty
# algorithm. Returing the conversion expression and attributes expression for use
# by the caller.
# 2.1 Let operation be the operation used to declare the named property getter.
# 2.2 Let value be an uninitialized variable.
# 2.3 If operation was defined without an identifier, then set value to the result
# of performing the steps listed in the interface description to determine the
# value of a named property with P as the name.
# 2.4 Otherwise, operation was defined with an identifier. Set value to the result
# of performing the steps listed in the description of operation with P as the
# only argument value..
# 2.5 Let desc be a newly created Property Descriptor with no fields.
# 2.6 Set desc.[[Value]] to the result of converting value to an ECMAScript value.
# 2.7 If O implements an interface with a named property setter, then set desc.[[Writable]]
# to true, otherwise set it to false.
# 2.8 If O implements an interface with the [LegacyUnenumerableNamedProperties]
# extended attribute, then set desc.[[Enumerable]] to false, otherwise set it
# to true.
# 2.9 Set desc.[[Configurable]] to true.
# 2.10 Return desc.
my @attributes = ();
push(@attributes, "JSC::PropertyAttribute::ReadOnly") if !GetNamedSetterOperation($interface) && !$interface->extendedAttributes->{Plugin};
push(@attributes, "JSC::PropertyAttribute::DontEnum") if $interface->extendedAttributes->{LegacyUnenumerableNamedProperties};
my $attributeString = "static_cast<unsigned>(" . ((@attributes > 0) ? join(" | ", @attributes) : "0") . ")";
my $nativeToJSConversion = NativeToJSValueUsingPointers($namedGetterOperation, $interface, $namedPropertyExpression, "*thisObject->globalObject()");
return ($nativeToJSConversion, $attributeString);
}
sub GenerateNamedGetterLambda
{
my ($outputArray, $interface, $className, $namedGetterOperation, $namedGetterFunctionName, $IDLType) = @_;
my @arguments = GenerateCallWithUsingReferences($namedGetterOperation->extendedAttributes->{CallWith}, $outputArray, "std::nullopt", "thisObject", "", " ");
push(@arguments, "propertyNameToAtomString(propertyName)");
push(@$outputArray, " auto getterFunctor = visibleNamedPropertyItemAccessorFunctor<${IDLType}, ${className}>([] (${className}& thisObject, PropertyName propertyName) -> decltype(auto) {\n");
push(@$outputArray, " return thisObject.wrapped().${namedGetterFunctionName}(" . join(", ", @arguments) . ");\n");
push(@$outputArray, " });\n");
}
# https://heycam.github.io/webidl/#legacy-platform-object-getownproperty
sub GenerateGetOwnPropertySlot
{
my ($outputArray, $interface, $className) = @_;
return if $interface->extendedAttributes->{CustomGetOwnPropertySlot};
my $namedGetterOperation = GetNamedGetterOperation($interface);
my $indexedGetterOperation = GetIndexedGetterOperation($interface);
push(@$outputArray, "bool ${className}::getOwnPropertySlot(JSObject* object, JSGlobalObject* lexicalGlobalObject, PropertyName propertyName, PropertySlot& slot)\n");
push(@$outputArray, "{\n");
if ($namedGetterOperation || $indexedGetterOperation) {
push(@$outputArray, " auto throwScope = DECLARE_THROW_SCOPE(JSC::getVM(lexicalGlobalObject));\n");
}
push(@$outputArray, " auto* thisObject = jsCast<${className}*>(object);\n");
push(@$outputArray, " ASSERT_GC_OBJECT_INHERITS(thisObject, info());\n");
# NOTE: The alogithm for [[GetOwnProperty]] contains only the following step:
# 1. Return LegacyPlatformObjectGetOwnProperty(O, P, false).
# Therefore, the following steps are from the LegacyPlatformObjectGetOwnProperty algorithm
# https://heycam.github.io/webidl/#LegacyPlatformObjectGetOwnProperty
# 1. If O supports indexed properties and P is an array index property name, then:
if ($indexedGetterOperation) {
# 1.1. Let index be the result of calling ToUint32(P).
push(@$outputArray, " if (auto index = parseIndex(propertyName)) {\n");
# 1.2. If index is a supported property index, then:
# FIXME: This should support non-contiguous indices.
push(@$outputArray, " if (index.value() < thisObject->wrapped().length()) {\n");
# NOTE: GenerateIndexedGetter implements steps 1.2.1 - 1.2.8.
my ($nativeToJSConversion, $attributeString) = GenerateIndexedGetter($interface, $indexedGetterOperation, "index.value()");
push(@$outputArray, " auto value = ${nativeToJSConversion};\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, false);\n");
push(@$outputArray, " slot.setValue(thisObject, ${attributeString}, value);\n");
push(@$outputArray, " return true;\n");
push(@$outputArray, " }\n");
# 1.3. Set ignoreNamedProps to true.
# NOTE: Setting ignoreNamedProps has the effect of skipping step 2, so we can early return here
# rather than going through the paces of having an actual ignoreNamedProps update.
if ($namedGetterOperation || $interface->extendedAttributes->{Plugin}) {
push(@$outputArray, " return JSObject::getOwnPropertySlot(object, lexicalGlobalObject, propertyName, slot);\n");
}
push(@$outputArray, " }\n");
}
# 2. If O supports named properties, the result of running the named property visibility
# algorithm with property name P and object O is true, and ignoreNamedProps is false, then:
if ($namedGetterOperation) {
# NOTE: ignoreNamedProps is guarenteed to be false here, as it is initially false, and never set
# to true, due to the early return in step 1.3
AddToImplIncludes("JSDOMAbstractOperations.h");
my $namedGetterFunctionName = $namedGetterOperation->extendedAttributes->{ImplementedAs} || $namedGetterOperation->name || "namedItem";
my $IDLType = GetIDLTypeExcludingNullability($interface, $namedGetterOperation->type);
push(@$outputArray, " using GetterIDLType = ${IDLType};\n");
GenerateNamedGetterLambda($outputArray, $interface, $className, $namedGetterOperation, $namedGetterFunctionName, "GetterIDLType");
my $overrideBuiltin = $codeGenerator->InheritsExtendedAttribute($interface, "LegacyOverrideBuiltIns") ? "LegacyOverrideBuiltIns::Yes" : "LegacyOverrideBuiltIns::No";
push(@$outputArray, " if (auto namedProperty = accessVisibleNamedProperty<${overrideBuiltin}>(*lexicalGlobalObject, *thisObject, propertyName, getterFunctor)) {\n");
# NOTE: GenerateNamedGetter implements steps 2.1 - 2.10.
my ($nativeToJSConversion, $attributeString) = GenerateNamedGetter($interface, $namedGetterOperation, "WTFMove(namedProperty.value())");
push(@$outputArray, " auto value = ${nativeToJSConversion};\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, false);\n");
push(@$outputArray, " slot.setValue(thisObject, ${attributeString}, value);\n");
push(@$outputArray, " return true;\n");
push(@$outputArray, " }\n");
}
if ($interface->extendedAttributes->{Plugin}) {
AddToImplIncludes("JSPluginElementFunctions.h");
push(@$outputArray, " if (pluginElementCustomGetOwnPropertySlot(thisObject, lexicalGlobalObject, propertyName, slot))\n");
push(@$outputArray, " return true;\n");
push(@$outputArray, " ASSERT(slot.isTaintedByOpaqueObject());\n");
push(@$outputArray, " if (slot.isVMInquiry())\n");
push(@$outputArray, " return false;\n");
}
# 3. Return OrdinaryGetOwnProperty(O, P).
push(@$outputArray, " return JSObject::getOwnPropertySlot(object, lexicalGlobalObject, propertyName, slot);\n");
push(@$outputArray, "}\n\n");
}
# https://heycam.github.io/webidl/#legacy-platform-object-getownproperty
sub GenerateGetOwnPropertySlotByIndex
{
my ($outputArray, $interface, $className) = @_;
return if $interface->extendedAttributes->{CustomGetOwnPropertySlot};
# Sink the int-to-string conversion that happens when we create a PropertyName
# to the point where we actually need it.
my $didGeneratePropertyName = 0;
my $propertyNameGeneration = sub {
return if $didGeneratePropertyName;
push(@$outputArray, " auto propertyName = Identifier::from(vm, index);\n");
$didGeneratePropertyName = 1;
};
my $namedGetterOperation = GetNamedGetterOperation($interface);
my $indexedGetterOperation = GetIndexedGetterOperation($interface);
push(@$outputArray, "bool ${className}::getOwnPropertySlotByIndex(JSObject* object, JSGlobalObject* lexicalGlobalObject, unsigned index, PropertySlot& slot)\n");
push(@$outputArray, "{\n");
push(@$outputArray, " VM& vm = JSC::getVM(lexicalGlobalObject);\n");
if ($namedGetterOperation || $indexedGetterOperation) {
push(@$outputArray, " auto throwScope = DECLARE_THROW_SCOPE(vm);\n");
}
push(@$outputArray, " auto* thisObject = jsCast<${className}*>(object);\n");
push(@$outputArray, " ASSERT_GC_OBJECT_INHERITS(thisObject, info());\n");
# NOTE: The alogithm for [[GetOwnProperty]] contains only the following step:
# 1. Return LegacyPlatformObjectGetOwnProperty(O, P, false).
# Therefore, the following steps are from the LegacyPlatformObjectGetOwnProperty algorithm
# https://heycam.github.io/webidl/#LegacyPlatformObjectGetOwnProperty
# 1. If O supports indexed properties and P is an array index property name, then:
if ($indexedGetterOperation) {
# 1.1. Let index be the result of calling ToUint32(P).
push(@$outputArray, " if (LIKELY(index <= MAX_ARRAY_INDEX)) {\n");
# 1.2. If index is a supported property index, then:
# FIXME: This should support non-contiguous indices.
push(@$outputArray, " if (index < thisObject->wrapped().length()) {\n");
# NOTE: GenerateIndexedGetter implements steps 1.2.1 - 1.2.8.
my ($nativeToJSConversion, $attributeString) = GenerateIndexedGetter($interface, $indexedGetterOperation, "index");
push(@$outputArray, " auto value = ${nativeToJSConversion};\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, false);\n");
push(@$outputArray, " slot.setValue(thisObject, ${attributeString}, value);\n");
push(@$outputArray, " return true;\n");
push(@$outputArray, " }\n");
# 1.3. Set ignoreNamedProps to true.
# NOTE: Setting ignoreNamedProps has the effect of skipping step 2, so we can early return here
# rather than going through the paces of having an actual ignoreNamedProps update.
if ($namedGetterOperation || $interface->extendedAttributes->{Plugin}) {
push(@$outputArray, " return JSObject::getOwnPropertySlotByIndex(object, lexicalGlobalObject, index, slot);\n");
}
push(@$outputArray, " }\n");
}
# 2. If O supports named properties, the result of running the named property visibility
# algorithm with property name P and object O is true, and ignoreNamedProps is false, then:
if ($namedGetterOperation) {
# NOTE: ignoreNamedProps is guarenteed to be false here, as it is initially false, and never set
# to true, due to the early return in step 1.3
AddToImplIncludes("JSDOMAbstractOperations.h");
&$propertyNameGeneration();
my $namedGetterFunctionName = $namedGetterOperation->extendedAttributes->{ImplementedAs} || $namedGetterOperation->name || "namedItem";
my $IDLType = GetIDLTypeExcludingNullability($interface, $namedGetterOperation->type);
push(@$outputArray, " using GetterIDLType = ${IDLType};\n");
GenerateNamedGetterLambda($outputArray, $interface, $className, $namedGetterOperation, $namedGetterFunctionName, "GetterIDLType");
my $overrideBuiltin = $codeGenerator->InheritsExtendedAttribute($interface, "LegacyOverrideBuiltIns") ? "LegacyOverrideBuiltIns::Yes" : "LegacyOverrideBuiltIns::No";
push(@$outputArray, " if (auto namedProperty = accessVisibleNamedProperty<${overrideBuiltin}>(*lexicalGlobalObject, *thisObject, propertyName, getterFunctor)) {\n");
# NOTE: GenerateNamedGetter implements steps 2.1 - 2.10.
my ($nativeToJSConversion, $attributeString) = GenerateNamedGetter($interface, $namedGetterOperation, "WTFMove(namedProperty.value())");
push(@$outputArray, " auto value = ${nativeToJSConversion};\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, false);\n");
push(@$outputArray, " slot.setValue(thisObject, ${attributeString}, value);\n");
push(@$outputArray, " return true;\n");
push(@$outputArray, " }\n");
}
if ($interface->extendedAttributes->{Plugin}) {
&$propertyNameGeneration();
AddToImplIncludes("JSPluginElementFunctions.h");
push(@$outputArray, " if (pluginElementCustomGetOwnPropertySlot(thisObject, lexicalGlobalObject, propertyName, slot))\n");
push(@$outputArray, " return true;\n");
push(@$outputArray, " ASSERT(slot.isTaintedByOpaqueObject());\n");
push(@$outputArray, " if (slot.isVMInquiry())\n");
push(@$outputArray, " return false;\n");
}
# 3. Return OrdinaryGetOwnProperty(O, P).
push(@$outputArray, " return JSObject::getOwnPropertySlotByIndex(object, lexicalGlobalObject, index, slot);\n");
push(@$outputArray, "}\n\n");
}
# https://heycam.github.io/webidl/#legacy-platform-object-property-enumeration
sub GenerateGetOwnPropertyNames
{
my ($outputArray, $interface, $className) = @_;
return if $interface->extendedAttributes->{CustomGetOwnPropertyNames};
my $namedGetterOperation = GetNamedGetterOperation($interface);
my $indexedGetterOperation = GetIndexedGetterOperation($interface);
push(@$outputArray, "void ${className}::getOwnPropertyNames(JSObject* object, JSGlobalObject* lexicalGlobalObject, PropertyNameArray& propertyNames, DontEnumPropertiesMode mode)\n");
push(@$outputArray, "{\n");
if ($indexedGetterOperation || $namedGetterOperation) {
push(@$outputArray, " VM& vm = JSC::getVM(lexicalGlobalObject);\n");
}
push(@$outputArray, " auto* thisObject = jsCast<${className}*>(object);\n");
push(@$outputArray, " ASSERT_GC_OBJECT_INHERITS(object, info());\n");
# 1. If the object supports indexed properties, then the objects supported
# property indices are enumerated first, in numerical order.
# FIXME: This should support non-contiguous indices.
if ($indexedGetterOperation) {
push(@$outputArray, " for (unsigned i = 0, count = thisObject->wrapped().length(); i < count; ++i)\n");
push(@$outputArray, " propertyNames.add(Identifier::from(vm, i));\n");
}
# 2. If the object supports named properties and doesnt implement an interface
# with the [LegacyUnenumerableNamedProperties] extended attribute, then the
# objects supported property names that are visible according to the named
# property visibility algorithm are enumerated next, in the order given in
# the definition of the set of supported property names.
if ($namedGetterOperation) {
if (!$interface->extendedAttributes->{LegacyUnenumerableNamedProperties}) {
push(@$outputArray, " for (auto& propertyName : thisObject->wrapped().supportedPropertyNames())\n");
push(@$outputArray, " propertyNames.add(Identifier::fromString(vm, propertyName));\n");
} else {
push(@$outputArray, " if (mode == DontEnumPropertiesMode::Include) {\n");
push(@$outputArray, " for (auto& propertyName : thisObject->wrapped().supportedPropertyNames())\n");
push(@$outputArray, " propertyNames.add(Identifier::fromString(vm, propertyName));\n");
push(@$outputArray, " }\n");
}
}
# 3. Finally, any enumerable own properties or properties from the objects
# prototype chain are then enumerated, in no defined order.
push(@$outputArray, " JSObject::getOwnPropertyNames(object, lexicalGlobalObject, propertyNames, mode);\n");
push(@$outputArray, "}\n\n");
}
# https://heycam.github.io/webidl/#invoke-indexed-setter
sub GenerateInvokeIndexedPropertySetter
{
my ($outputArray, $indent, $interface, $indexedSetterOperation, $indexExpression, $value) = @_;
# The second argument of the indexed setter operation is the argument being converted.
my $argument = @{$indexedSetterOperation->arguments}[1];
my $nativeValue = JSValueToNative($interface, $argument, $value, $indexedSetterOperation->extendedAttributes->{Conditional}, "lexicalGlobalObject", "*lexicalGlobalObject", "thisObject", "", "");
push(@$outputArray, $indent . "auto nativeValue = ${nativeValue};\n");
push(@$outputArray, $indent . "RETURN_IF_EXCEPTION(throwScope, true);\n");
my $indexedSetterFunctionName = $indexedSetterOperation->name || "setItem";
my $nativeValuePassExpression = PassArgumentExpression("nativeValue", $argument);
my $functionString = "thisObject->wrapped().${indexedSetterFunctionName}(${indexExpression}, ${nativeValuePassExpression})";
push(@$outputArray, $indent . "invokeFunctorPropagatingExceptionIfNecessary(*lexicalGlobalObject, throwScope, [&] { return ${functionString}; });\n");
}
# https://heycam.github.io/webidl/#invoke-named-setter
sub GenerateInvokeNamedPropertySetter
{
my ($outputArray, $indent, $interface, $namedSetterOperation, $value) = @_;
my $argument = @{$namedSetterOperation->arguments}[1];
my $nativeValue = JSValueToNative($interface, $argument, $value, $namedSetterOperation->extendedAttributes->{Conditional}, "lexicalGlobalObject", "*lexicalGlobalObject", "thisObject", "", "");
push(@$outputArray, $indent . "auto nativeValue = ${nativeValue};\n");
push(@$outputArray, $indent . "RETURN_IF_EXCEPTION(throwScope, true);\n");
my $namedSetterFunctionName = $namedSetterOperation->name || "setNamedItem";
my $nativeValuePassExpression = PassArgumentExpression("nativeValue", $argument);
my $functionString = "thisObject->wrapped().${namedSetterFunctionName}(propertyNameToString(propertyName), ${nativeValuePassExpression})";
push(@$outputArray, $indent . "invokeFunctorPropagatingExceptionIfNecessary(*lexicalGlobalObject, throwScope, [&] { return ${functionString}; });\n");
}
sub GeneratePut
{
my ($outputArray, $interface, $className) = @_;
return if $interface->extendedAttributes->{CustomPut};
my $namedSetterOperation = GetNamedSetterOperation($interface);
my $indexedSetterOperation = GetIndexedSetterOperation($interface);
push(@$outputArray, "bool ${className}::put(JSCell* cell, JSGlobalObject* lexicalGlobalObject, PropertyName propertyName, JSValue value, PutPropertySlot& putPropertySlot)\n");
push(@$outputArray, "{\n");
push(@$outputArray, " auto* thisObject = jsCast<${className}*>(cell);\n");
push(@$outputArray, " ASSERT_GC_OBJECT_INHERITS(thisObject, info());\n\n");
push(@$outputArray, " if (UNLIKELY(thisObject != putPropertySlot.thisValue()))\n");
push(@$outputArray, " return JSObject::put(thisObject, lexicalGlobalObject, propertyName, value, putPropertySlot);\n");
push(@$outputArray, " auto throwScope = DECLARE_THROW_SCOPE(lexicalGlobalObject->vm());\n\n");
assert("CEReactions is not supported on having both named setters and indexed setters") if $namedSetterOperation && $namedSetterOperation->extendedAttributes->{CEReactions}
&& $indexedSetterOperation && $indexedSetterOperation->extendedAttributes->{CEReactions};
if ($namedSetterOperation) {
GenerateCustomElementReactionsStackIfNeeded($outputArray, $namedSetterOperation, "*lexicalGlobalObject");
}
if ($indexedSetterOperation) {
GenerateCustomElementReactionsStackIfNeeded($outputArray, $indexedSetterOperation, "*lexicalGlobalObject");
}
if ($indexedSetterOperation) {
push(@$outputArray, " if (auto index = parseIndex(propertyName)) {\n");
GenerateInvokeIndexedPropertySetter($outputArray, " ", $interface, $indexedSetterOperation, "index.value()", "value");
push(@$outputArray, " return true;\n");
push(@$outputArray, " }\n\n");
}
if ($namedSetterOperation) {
# FIMXE: We need a more comprehensive story for Symbols.
push(@$outputArray, " if (!propertyName.isSymbol()) {\n");
my $additionalIndent = "";
my $legacyOverrideBuiltins = $codeGenerator->InheritsExtendedAttribute($interface, "LegacyOverrideBuiltIns");
if (!$legacyOverrideBuiltins) {
push(@$outputArray, " PropertySlot slot { thisObject, PropertySlot::InternalMethodType::VMInquiry, &lexicalGlobalObject->vm() };\n");
push(@$outputArray, " JSValue prototype = thisObject->getPrototypeDirect(JSC::getVM(lexicalGlobalObject));\n");
push(@$outputArray, " bool found = prototype.isObject() && asObject(prototype)->getPropertySlot(lexicalGlobalObject, propertyName, slot);\n");
push(@$outputArray, " slot.disallowVMEntry.reset();\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, false);\n");
push(@$outputArray, " if (!found) {\n");
$additionalIndent .= " ";
}
GenerateInvokeNamedPropertySetter($outputArray, $additionalIndent . " ", $interface, $namedSetterOperation, "value");
push(@$outputArray, $additionalIndent . " return true;\n");
if (!$legacyOverrideBuiltins) {
push(@$outputArray, " }\n");
}
push(@$outputArray, " }\n\n");
}
assert("Using both a named property setter and [Plugin] together is not supported.") if $namedSetterOperation && $interface->extendedAttributes->{Plugin};
if ($interface->extendedAttributes->{Plugin}) {
AddToImplIncludes("JSPluginElementFunctions.h");
push(@$outputArray, " bool putResult = false;\n");
push(@$outputArray, " bool success = pluginElementCustomPut(thisObject, lexicalGlobalObject, propertyName, value, putPropertySlot, putResult);\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, false);\n");
push(@$outputArray, " if (success)\n");
push(@$outputArray, " return putResult;\n\n");
}
push(@$outputArray, " throwScope.assertNoException();\n");
push(@$outputArray, " RELEASE_AND_RETURN(throwScope, JSObject::put(thisObject, lexicalGlobalObject, propertyName, value, putPropertySlot));\n");
push(@$outputArray, "}\n\n");
}
sub GeneratePutByIndex
{
my ($outputArray, $interface, $className) = @_;
return if $interface->extendedAttributes->{CustomPut};
my $namedSetterOperation = GetNamedSetterOperation($interface);
my $indexedSetterOperation = GetIndexedSetterOperation($interface);
my $legacyOverrideBuiltins = $codeGenerator->InheritsExtendedAttribute($interface, "LegacyOverrideBuiltIns");
my $ellidesCallsToBase = ($namedSetterOperation && $legacyOverrideBuiltins) && !$interface->extendedAttributes->{Plugin};
push(@$outputArray, "bool ${className}::putByIndex(JSCell* cell, JSGlobalObject* lexicalGlobalObject, unsigned index, JSValue value, bool" . (!$ellidesCallsToBase ? " shouldThrow" : "") . ")\n");
push(@$outputArray, "{\n");
push(@$outputArray, " auto* thisObject = jsCast<${className}*>(cell);\n");
push(@$outputArray, " ASSERT_GC_OBJECT_INHERITS(thisObject, info());\n\n");
push(@$outputArray, " VM& vm = JSC::getVM(lexicalGlobalObject);\n");
push(@$outputArray, " auto throwScope = DECLARE_THROW_SCOPE(vm);\n\n");
assert("CEReactions is not supported on having both named setters and indexed setters") if $namedSetterOperation && $namedSetterOperation->extendedAttributes->{CEReactions}
&& $indexedSetterOperation && $indexedSetterOperation->extendedAttributes->{CEReactions};
if ($namedSetterOperation) {
GenerateCustomElementReactionsStackIfNeeded($outputArray, $namedSetterOperation, "*lexicalGlobalObject");
}
if ($indexedSetterOperation) {
GenerateCustomElementReactionsStackIfNeeded($outputArray, $indexedSetterOperation, "*lexicalGlobalObject");
}
if ($indexedSetterOperation) {
push(@$outputArray, " if (LIKELY(index <= MAX_ARRAY_INDEX)) {\n");
GenerateInvokeIndexedPropertySetter($outputArray, " ", $interface, $indexedSetterOperation, "index", "value");
push(@$outputArray, " return true;\n");
push(@$outputArray, " }\n\n");
}
if ($namedSetterOperation) {
push(@$outputArray, " auto propertyName = Identifier::from(vm, index);\n");
my $additionalIndent = "";
if (!$legacyOverrideBuiltins) {
push(@$outputArray, " PropertySlot slot { thisObject, PropertySlot::InternalMethodType::VMInquiry, &vm };\n");
push(@$outputArray, " JSValue prototype = thisObject->getPrototypeDirect(vm);\n");
push(@$outputArray, " bool found = prototype.isObject() && asObject(prototype)->getPropertySlot(lexicalGlobalObject, propertyName, slot);\n");
push(@$outputArray, " slot.disallowVMEntry.reset();\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, false);\n");
push(@$outputArray, " if (!found) {\n");
$additionalIndent .= " ";
}
GenerateInvokeNamedPropertySetter($outputArray, $additionalIndent . " ", $interface, $namedSetterOperation, "value");
push(@$outputArray, $additionalIndent . " return true;\n");
if (!$legacyOverrideBuiltins) {
push(@$outputArray, " }\n\n");
}
}
assert("Using both a named property setter and [Plugin] together is not supported.") if $namedSetterOperation && $interface->extendedAttributes->{Plugin};
if ($interface->extendedAttributes->{Plugin}) {
AddToImplIncludes("JSPluginElementFunctions.h");
push(@$outputArray, " auto propertyName = Identifier::from(vm, index);\n");
push(@$outputArray, " PutPropertySlot putPropertySlot(thisObject, shouldThrow);\n");
push(@$outputArray, " bool putResult = false;\n");
push(@$outputArray, " bool success = pluginElementCustomPut(thisObject, lexicalGlobalObject, propertyName, value, putPropertySlot, putResult);\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, false);\n");
push(@$outputArray, " if (success)\n");
push(@$outputArray, " return putResult;\n\n");
}
if (!$ellidesCallsToBase) {
push(@$outputArray, " throwScope.assertNoException();\n");
push(@$outputArray, " RELEASE_AND_RETURN(throwScope, JSObject::putByIndex(cell, lexicalGlobalObject, index, value, shouldThrow));\n");
}
push(@$outputArray, "}\n\n");
}
sub GenerateIsLegacyUnforgeablePropertyName
{
my ($outputArray, $interface) = @_;
my @unforgeablePropertyNames = ();
foreach my $property (@{$interface->attributes}, @{$interface->operations}) {
next if $property->isStatic;
if (IsLegacyUnforgeable($interface, $property)) {
push(@unforgeablePropertyNames, $property->name);
}
}
return 0 if (scalar(@unforgeablePropertyNames) == 0);
my $condition = join(" || ", map { "propertyName == \"" . $_ . "\"" } @unforgeablePropertyNames);
push(@$outputArray, "static bool isLegacyUnforgeablePropertyName(PropertyName propertyName)\n");
push(@$outputArray, "{\n");
push(@$outputArray, " return ${condition};\n");
push(@$outputArray, "}\n\n");
return 1;
}
# https://heycam.github.io/webidl/#legacy-platform-object-defineownproperty
sub GenerateDefineOwnProperty
{
my ($outputArray, $interface, $className) = @_;
return if $interface->extendedAttributes->{CustomDefineOwnProperty};
my $namedSetterOperation = GetNamedSetterOperation($interface);
my $indexedSetterOperation = GetIndexedSetterOperation($interface);
return if !$namedSetterOperation && !$indexedSetterOperation;
push(@$outputArray, "bool ${className}::defineOwnProperty(JSObject* object, JSGlobalObject* lexicalGlobalObject, PropertyName propertyName, const PropertyDescriptor& propertyDescriptor, bool shouldThrow)\n");
push(@$outputArray, "{\n");
push(@$outputArray, " auto* thisObject = jsCast<${className}*>(object);\n");
push(@$outputArray, " ASSERT_GC_OBJECT_INHERITS(thisObject, info());\n\n");
push(@$outputArray, " auto throwScope = DECLARE_THROW_SCOPE(lexicalGlobalObject->vm());\n\n");
assert("CEReactions is not supported on having both named setters and indexed setters") if $namedSetterOperation && $namedSetterOperation->extendedAttributes->{CEReactions}
&& $indexedSetterOperation && $indexedSetterOperation->extendedAttributes->{CEReactions};
if ($namedSetterOperation) {
GenerateCustomElementReactionsStackIfNeeded($outputArray, $namedSetterOperation, "*lexicalGlobalObject");
}
if ($indexedSetterOperation) {
GenerateCustomElementReactionsStackIfNeeded($outputArray, $indexedSetterOperation, "*lexicalGlobalObject");
}
# 1. If O supports indexed properties and P is an array index property name, then:
if (GetIndexedGetterOperation($interface)) {
# NOTE: The numbers are out of order because there is no reason doing steps 1, 3, and 4 if there
# is no indexed property setter.
if (!$indexedSetterOperation) {
# 2. If O does not implement an interface with an indexed property setter, then return false.
push(@$outputArray, " if (parseIndex(propertyName))\n");
push(@$outputArray, " return false;\n\n");
} else {
push(@$outputArray, " if (auto index = parseIndex(propertyName)) {\n");
# 1. If the result of calling IsDataDescriptor(Desc) is false, then return false.
push(@$outputArray, " if (!propertyDescriptor.isDataDescriptor())\n");
push(@$outputArray, " return false;\n");
# 3. Invoke the indexed property setter with P and Desc.[[Value]].
GenerateInvokeIndexedPropertySetter($outputArray, " ", $interface, $indexedSetterOperation, "index.value()", "propertyDescriptor.value()");
# 4. Return true.
push(@$outputArray, " return true;\n");
push(@$outputArray, " }\n\n");
}
}
# 2. If O supports named properties, O does not implement an interface with the [Global]
# extended attribute and P is not an unforgeable property name of O, then:
if (GetNamedGetterOperation($interface) && !IsGlobalInterface($interface)) {
# FIMXE: We need a more comprehensive story for Symbols.
push(@$outputArray, " if (!propertyName.isSymbol()) {\n");
my $additionalIndent = "";
my $hasUnforgableProperties = GenerateIsLegacyUnforgeablePropertyName($outputArray, $interface);
if ($hasUnforgableProperties) {
push(@$outputArray, " if (!isLegacyUnforgeablePropertyName(propertyName)) {\n");
$additionalIndent .= " ";
}
# 1. Let creating be true if P is not a supported property name, and false otherwise.
# NOTE: This step is strength reduced into the only use of 'creating' in step 2.2.1
# 2. If O implements an interface with the [LegacyOverrideBuiltIns] extended attribute or O
# does not have an own property named P, then:
my $legacyOverrideBuiltins = $codeGenerator->InheritsExtendedAttribute($interface, "LegacyOverrideBuiltIns");
if (!$legacyOverrideBuiltins) {
# FIXME: Is JSObject::getOwnPropertySlot the right function to call? Is there a function that will
# only look at the actual properties, and not call into our implementation of the
# [[GetOwnProperty]] hook?
push(@$outputArray, $additionalIndent. " PropertySlot slot { thisObject, PropertySlot::InternalMethodType::VMInquiry, &lexicalGlobalObject->vm() };\n");
push(@$outputArray, $additionalIndent. " bool found = JSObject::getOwnPropertySlot(thisObject, lexicalGlobalObject, propertyName, slot);\n");
push(@$outputArray, $additionalIndent. " slot.disallowVMEntry.reset();\n");
push(@$outputArray, $additionalIndent. " RETURN_IF_EXCEPTION(throwScope, false);\n");
push(@$outputArray, $additionalIndent. " if (!found) {\n");
$additionalIndent .= " ";
}
if (!$namedSetterOperation) {
# 2.1. If creating is false and O does not implement an interface with a named property setter, then return false.
push(@$outputArray, $additionalIndent . " if (thisObject->wrapped().isSupportedPropertyName(propertyNameToString(propertyName)))\n");
push(@$outputArray, $additionalIndent . " return false;\n");
} else {
# 2.2. If O implements an interface with a named property setter, then:
# 2.2.1. If the result of calling IsDataDescriptor(Desc) is false, then return false.
push(@$outputArray, $additionalIndent . " if (!propertyDescriptor.isDataDescriptor())\n");
push(@$outputArray, $additionalIndent . " return false;\n");
# 2.2.2. Invoke the named property setter with P and Desc.[[Value]].
GenerateInvokeNamedPropertySetter($outputArray, $additionalIndent . " ", $interface, $namedSetterOperation, "propertyDescriptor.value()");
# 2.2.3. Return true.
push(@$outputArray, $additionalIndent . " return true;\n");
}
if (!$legacyOverrideBuiltins) {
push(@$outputArray, $additionalIndent . " }\n");
}
if ($hasUnforgableProperties) {
push(@$outputArray, " }\n");
}
# Close the !propertyName.isSymbol() condition.
push(@$outputArray, " }\n\n");
}
push(@$outputArray, " PropertyDescriptor newPropertyDescriptor = propertyDescriptor;\n");
# 3. If O does not implement an interface with the [Global] extended attribute,
# then set Desc.[[Configurable]] to true.
if (!IsGlobalInterface($interface)) {
push(@$outputArray, " newPropertyDescriptor.setConfigurable(true);\n");
}
# 4. Return OrdinaryDefineOwnProperty(O, P, Desc).
push(@$outputArray, " throwScope.release();\n");
push(@$outputArray, " return JSObject::defineOwnProperty(object, lexicalGlobalObject, propertyName, newPropertyDescriptor, shouldThrow);\n");
push(@$outputArray, "}\n\n");
}
sub GenerateDeletePropertyCommon
{
my ($outputArray, $interface, $className, $operation, $conditional) = @_;
# This implements step 2 of https://heycam.github.io/webidl/#legacy-platform-object-delete
# so it can be shared between the generation of deleteProperty and deletePropertyByIndex.
# 2. If O supports named properties, O does not implement an interface with the
# [Global] extended attribute and the result of calling the named
# property visibility algorithm with property name P and object O is true, then:
assert("Named property deleters are not allowed without a corresponding named property getter.") if !GetNamedGetterOperation($interface);
assert("Named property deleters are not allowed on global object interfaces.") if IsGlobalInterface($interface);
AddToImplIncludes("JSDOMAbstractOperations.h", $conditional);
my $overrideBuiltin = $codeGenerator->InheritsExtendedAttribute($interface, "LegacyOverrideBuiltIns") ? "LegacyOverrideBuiltIns::Yes" : "LegacyOverrideBuiltIns::No";
push(@$outputArray, " if (isVisibleNamedProperty<${overrideBuiltin}>(*lexicalGlobalObject, thisObject, propertyName)) {\n");
GenerateCustomElementReactionsStackIfNeeded($outputArray, $operation, "*lexicalGlobalObject");
# 2.1. If O does not implement an interface with a named property deleter, then return false.
# 2.2. Let operation be the operation used to declare the named property deleter.
# NOTE: We only add a deleteProperty implementation of we have a named property deleter.
# 2.3. If operation was defined without an identifier, then:
# 1. Perform the steps listed in the interface description to delete an existing named
# property with P as the name.
# 2. If the steps indicated that the deletion failed, then return false.
# 2.4. Otherwise, operation was defined with an identifier:
# 1. Perform the steps listed in the description of operation with P as the only argument
# value.
# 2. If operation was declared with a return type of boolean and the steps returned false,
# then return false.
my $functionImplementationName = $operation->extendedAttributes->{ImplementedAs} || $codeGenerator->WK_lcfirst($operation->name) || "deleteNamedProperty";
my $functionCall = "impl." . $functionImplementationName . "(propertyNameToString(propertyName))";
# NOTE: We require the implementation function of named deleters without an identifier to
# return either bool or ExceptionOr<bool>.
if (!$operation->name) {
push(@$outputArray, " using ReturnType = decltype($functionCall);\n");
push(@$outputArray, " static_assert(std::is_same_v<ReturnType, ExceptionOr<bool>> || std::is_same_v<ReturnType, bool>, \"The implementation of named deleters without an identifer must return either bool or ExceptionOr<bool>.\");\n");
}
push(@$outputArray, " return performLegacyPlatformObjectDeleteOperation(*lexicalGlobalObject, [&] { return $functionCall; });\n");
push(@$outputArray, " }\n");
}
sub GenerateDeleteProperty
{
my ($outputArray, $interface, $className, $operation, $conditional) = @_;
# This implements https://heycam.github.io/webidl/#legacy-platform-object-delete for the
# for the deleteProperty override hook.
push(@$outputArray, "bool ${className}::deleteProperty(JSCell* cell, JSGlobalObject* lexicalGlobalObject, PropertyName propertyName, DeletePropertySlot& slot)\n");
push(@$outputArray, "{\n");
push(@$outputArray, " auto& thisObject = *jsCast<${className}*>(cell);\n");
push(@$outputArray, " auto& impl = thisObject.wrapped();\n");
# 1. If O supports indexed properties and P is an array index property name, then:
# 1. Let index be the result of calling ToUint32(P).
# 2. If index is not a supported property index, then return true.
# 3. Return false.
if (GetIndexedGetterOperation($interface)) {
push(@$outputArray, " if (auto index = parseIndex(propertyName))\n");
push(@$outputArray, " return !impl.isSupportedPropertyIndex(index.value());\n");
}
# GenerateDeletePropertyCommon implements step 2.
GenerateDeletePropertyCommon($outputArray, $interface, $className, $operation, $conditional);
# FIXME: Instead of calling down JSObject::deleteProperty, perhaps we should implement
# the remained of the algorithm ourselves.
push(@$outputArray, " return JSObject::deleteProperty(cell, lexicalGlobalObject, propertyName, slot);\n");
push(@$outputArray, "}\n\n");
}
sub GenerateDeletePropertyByIndex
{
my ($outputArray, $interface, $className, $operation, $conditional) = @_;
# This implements https://heycam.github.io/webidl/#legacy-platform-object-delete for the
# for the deletePropertyByIndex override hook.
push(@$outputArray, "bool ${className}::deletePropertyByIndex(JSCell* cell, JSGlobalObject* lexicalGlobalObject, unsigned index)\n");
push(@$outputArray, "{\n");
push(@$outputArray, " auto& thisObject = *jsCast<${className}*>(cell);\n");
push(@$outputArray, " auto& impl = thisObject.wrapped();\n");
# 1. If O supports indexed properties and P is an array index property name, then:
# 1. Let index be the result of calling ToUint32(P).
# 2. If index is not a supported property index, then return true.
# 3. Return false.
# NOTE: For deletePropertyByIndex, if there is an indexed getter, checking isSupportedPropertyIndex()
# is all that needs to be done, no need to generate the .
if (GetIndexedGetterOperation($interface)) {
push(@$outputArray, " return !impl.isSupportedPropertyIndex(index);\n");
} else {
push(@$outputArray, " VM& vm = JSC::getVM(lexicalGlobalObject);\n");
push(@$outputArray, " auto propertyName = Identifier::from(vm, index);\n");
# GenerateDeletePropertyCommon implements step 2.
GenerateDeletePropertyCommon($outputArray, $interface, $className, $operation, $conditional);
# FIXME: Instead of calling down JSObject::deletePropertyByIndex, perhaps we should implement
# the remaineder of the algoritm (steps 3 and 4) ourselves.
# 3. If O has an own property with name P, then:
# 1. If the property is not configurable, then return false.
# 2. Otherwise, remove the property from O.
# 3. Return true.
push(@$outputArray, " return JSObject::deletePropertyByIndex(cell, lexicalGlobalObject, index);\n");
}
push(@$outputArray, "}\n\n");
}
sub GenerateNamedDeleterDefinition
{
my ($outputArray, $interface, $className) = @_;
return if $interface->extendedAttributes->{CustomDeleteProperty};
my $namedDeleterOperation = GetNamedDeleterOperation($interface);
# This implements https://heycam.github.io/webidl/#legacy-platform-object-delete using
# the deleteProperty and deletePropertyByIndex override hooks.
assert("Named property deleters are not allowed without a corresponding named property getter.") if !GetNamedGetterOperation($interface);
assert("Named property deleters are not allowed on global object interfaces.") if IsGlobalInterface($interface);
my $conditional = $namedDeleterOperation->extendedAttributes->{Conditional};
if ($conditional) {
my $conditionalString = $codeGenerator->GenerateConditionalStringFromAttributeValue($conditional);
push(@$outputArray, "#if ${conditionalString}\n\n");;
}
GenerateDeleteProperty($outputArray, $interface, $className, $namedDeleterOperation, $conditional);
GenerateDeletePropertyByIndex($outputArray, $interface, $className, $namedDeleterOperation, $conditional);
push(@implContent, "#endif\n\n") if $conditional;
}
sub GenerateHeaderContentHeader
{
my $interface = shift;
my $className = "JS" . $interface->type->name;
my @headerContentHeader;
if ($interface->extendedAttributes->{AppleCopyright}) {
@headerContentHeader = split("\r", $beginAppleCopyrightForHeaderFiles);
} else {
@headerContentHeader = split("\r", $headerTemplate);
}
push(@headerContentHeader, "\n#pragma once\n\n");
my $conditionalString = $codeGenerator->GenerateConditionalString($interface);
push(@headerContentHeader, "#if ${conditionalString}\n\n") if $conditionalString;
return @headerContentHeader;
}
sub GenerateImplementationContentHeader
{
my $interface = shift;
my $className = "JS" . $interface->type->name;
my @implContentHeader;
if ($interface->extendedAttributes->{AppleCopyright}) {
@implContentHeader = split("\r", $beginAppleCopyrightForSourceFiles);
} else {
@implContentHeader = split("\r", $headerTemplate);
}
push(@implContentHeader, "\n#include \"config.h\"\n");
my $conditionalString = $codeGenerator->GenerateConditionalString($interface);
push(@implContentHeader, "\n#if ${conditionalString}\n\n") if $conditionalString;
push(@implContentHeader, "#include \"$className.h\"\n\n");
return @implContentHeader;
}
sub NeedsImplementationClass
{
my ($interface) = @_;
return 0 if $interface->extendedAttributes->{JSBuiltin};
return 0 if $interface->isNamespaceObject;
return 1;
}
sub ShouldGenerateToWrapped
{
my ($hasParent, $interface) = @_;
return 0 if not NeedsImplementationClass($interface);
return 1 if !$hasParent or $interface->extendedAttributes->{JSGenerateToNativeObject};
return 1 if $interface->parentType && $interface->parentType->name eq "EventTarget";
return 0;
}
sub ShouldGenerateWrapperOwnerCode
{
my ($hasParent, $interface) = @_;
return 0 if not NeedsImplementationClass($interface);
return 1 if !$hasParent;
return 1 if GetGenerateIsReachable($interface);
return 1 if GetCustomIsReachable($interface);
return 1 if $interface->extendedAttributes->{JSCustomFinalize};
return 1 if $codeGenerator->InheritsExtendedAttribute($interface, "ActiveDOMObject");
return 0;
}
sub ShouldGenerateToJSDeclaration
{
my ($hasParent, $interface) = @_;
return 0 if ($interface->extendedAttributes->{SuppressToJSObject});
return 0 if not NeedsImplementationClass($interface);
return 0 if IsDOMGlobalObject($interface);
return 1 if (!$hasParent or $interface->extendedAttributes->{JSGenerateToJSObject} or $interface->extendedAttributes->{CustomToJSObject});
return 1 if $interface->parentType && $interface->parentType->name eq "EventTarget";
return 1 if @{$interface->constructors} > 0 && !HasCustomConstructor($interface);
return 0;
}
sub ShouldGenerateToJSImplementation
{
my ($hasParent, $interface) = @_;
return 0 if not ShouldGenerateToJSDeclaration($hasParent, $interface);
return 1 if not $interface->extendedAttributes->{CustomToJSObject};
return 0;
}
sub GetTypeNameForDisplayInException
{
my ($type) = @_;
# FIXME: Add more type specializations.
return "(" . join(" or ", map { $_->name } GetFlattenedMemberTypes($type)) . ")" if $type->isUnion;
return $type->name;
}
sub GetArgumentExceptionFunction
{
my ($interface, $argument, $argumentIndex, $quotedFunctionName) = @_;
my $name = $argument->name;
my $visibleInterfaceName = $codeGenerator->GetVisibleInterfaceName($interface);
my $typeName = GetTypeNameForDisplayInException($argument->type);
if ($codeGenerator->IsCallbackInterface($argument->type)) {
return "throwArgumentMustBeObjectError(lexicalGlobalObject, scope, ${argumentIndex}, \"${name}\", \"${visibleInterfaceName}\", ${quotedFunctionName});";
}
if ($codeGenerator->IsCallbackFunction($argument->type)) {
return "throwArgumentMustBeFunctionError(lexicalGlobalObject, scope, ${argumentIndex}, \"${name}\", \"${visibleInterfaceName}\", ${quotedFunctionName});";
}
if ($codeGenerator->IsWrapperType($argument->type) || $codeGenerator->IsBufferSourceType($argument->type)) {
return "throwArgumentTypeError(lexicalGlobalObject, scope, ${argumentIndex}, \"${name}\", \"${visibleInterfaceName}\", ${quotedFunctionName}, \"${typeName}\");";
}
if ($codeGenerator->IsEnumType($argument->type)) {
my $className = GetEnumerationClassName($argument->type, $interface);
return "throwArgumentMustBeEnumError(lexicalGlobalObject, scope, ${argumentIndex}, \"${name}\", \"${visibleInterfaceName}\", ${quotedFunctionName}, expectedEnumerationValues<${className}>());";
}
return undef;
}
sub GetArgumentExceptionThrower
{
my ($interface, $argument, $argumentIndex, $quotedFunctionName) = @_;
my $functionCall = GetArgumentExceptionFunction($interface, $argument, $argumentIndex, $quotedFunctionName);
return "[](JSC::JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope) { " . $functionCall . " }" if $functionCall;
}
sub GetAttributeExceptionFunction
{
my ($interface, $attribute) = @_;
my $name = $attribute->name;
my $visibleInterfaceName = $codeGenerator->GetVisibleInterfaceName($interface);
my $typeName = GetTypeNameForDisplayInException($attribute->type);
if ($codeGenerator->IsWrapperType($attribute->type) || $codeGenerator->IsBufferSourceType($attribute->type)) {
return "throwAttributeTypeError(lexicalGlobalObject, scope, \"${visibleInterfaceName}\", \"${name}\", \"${typeName}\");";
}
}
sub GetAttributeExceptionThrower
{
my ($interface, $attribute) = @_;
my $functionCall = GetAttributeExceptionFunction($interface, $attribute);
return "[](JSC::JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope) { " . $functionCall . " }" if $functionCall;
}
sub PassArgumentExpression
{
my ($name, $context) = @_;
my $type = $context->type;
return "WTFMove(${name})" if $type->isNullable;
if ($codeGenerator->IsBufferSourceType($type)) {
return "*${name}" if $type->name eq "ArrayBuffer";
return "${name}.releaseNonNull()";
}
if ($codeGenerator->IsPromiseType($type)) {
return "WTFMove(${name})" if ref($context) eq "IDLArgument" && $context->isOptional;
return "${name}.releaseNonNull()";
}
if ($codeGenerator->IsCallbackInterface($type) || $codeGenerator->IsCallbackFunction($type)) {
return "WTFMove(${name})" if ref($context) eq "IDLArgument" && $context->isOptional;
return "${name}.releaseNonNull()";
}
if ($codeGenerator->IsWrapperType($type)) {
return "${name}" if ref($context) eq "IDLArgument" && $context->isOptional;
return "*${name}";
}
return "WTFMove(${name})";
}
sub MangleAttributeOrFunctionName
{
my ($name) = @_;
$name =~ s/-/_dash_/g;
return $name =~ /^_/ ? $name : '_' . $name;
}
sub GetAttributeGetterName
{
my ($interface, $className, $attribute) = @_;
return GetAttributeGetterName($interface, $className, GetSharedSyntheticAttribute($interface, $attribute)) if $attribute->extendedAttributes->{DelegateToSharedSyntheticAttribute};
return GetJSBuiltinFunctionName($className, $attribute) if IsJSBuiltin($interface, $attribute);
return $codeGenerator->WK_lcfirst($className) . "Constructor" . MangleAttributeOrFunctionName($attribute->name) if $attribute->isStatic;
return $codeGenerator->WK_lcfirst($className) . MangleAttributeOrFunctionName($attribute->name) . "Constructor" if $codeGenerator->IsConstructorType($attribute->type);
return $codeGenerator->WK_lcfirst($className) . MangleAttributeOrFunctionName($attribute->name);
}
sub GetAttributeSetterName
{
my ($interface, $className, $attribute) = @_;
return GetAttributeSetterName($interface, $className, GetSharedSyntheticAttribute($interface, $attribute)) if $attribute->extendedAttributes->{DelegateToSharedSyntheticAttribute};
return "set" . $codeGenerator->WK_ucfirst(GetJSBuiltinFunctionName($className, $attribute)) if IsJSBuiltin($interface, $attribute);
return "set" . $codeGenerator->WK_ucfirst($className) . "Constructor" . MangleAttributeOrFunctionName($attribute->name) if $attribute->isStatic;
return "set" . $codeGenerator->WK_ucfirst($className) . MangleAttributeOrFunctionName($attribute->name);
}
sub GetFunctionName
{
my ($interface, $className, $operation) = @_;
return GetJSBuiltinFunctionName($className, $operation) if IsJSBuiltin($interface, $operation);
my $functionName = $operation->name;
$functionName = "SymbolIterator" if $functionName eq "[Symbol.Iterator]";
my $kind = $operation->isStatic ? "Constructor" : (OperationShouldBeOnInstance($interface, $operation) ? "Instance" : "Prototype");
return $codeGenerator->WK_lcfirst($className) . $kind . "Function" . MangleAttributeOrFunctionName($functionName);
}
sub GetFullyQualifiedImplementationCallName
{
my ($interface, $property, $implementationName, $implExpression, $conditional) = @_;
my $implementedBy = $property->extendedAttributes->{ImplementedBy};
if ($implementedBy && !$property->extendedAttributes->{Reflect}) {
AddToImplIncludes("${implementedBy}.h", $conditional);
return "WebCore::${implementedBy}::${implementationName}";
}
if ($property->isStatic || $property->isConstructor) {
return $interface->type->name . "::${implementationName}";
}
if ($property->extendedAttributes->{ForwardToMapLike}) {
return "forward" . $codeGenerator->WK_ucfirst($property->name) . "ToMapLike";
}
if ($property->extendedAttributes->{ForwardToSetLike}) {
return "forward" . $codeGenerator->WK_ucfirst($property->name) . "ToSetLike";
}
return "${implExpression}.${implementationName}";
}
sub AddAdditionalArgumentsForImplementationCall
{
my ($arguments, $interface, $property, $implExpression, $globalObject, $callFrame, $thisObjectExpression) = @_;
if ($property->extendedAttributes->{ImplementedBy} && !$property->isStatic && !$property->extendedAttributes->{Reflect}) {
unshift(@$arguments, $implExpression);
}
if ($property->extendedAttributes->{ForwardToMapLike} or $property->extendedAttributes->{ForwardToSetLike}) {
push(@$arguments, $globalObject);
if (ref($property) eq "IDLOperation") {
push(@$arguments, $callFrame);
}
push(@$arguments, $thisObjectExpression);
}
}
sub GetSpecialAccessorOperationForType
{
my ($interface, $special, $firstParameterType, $numberOfParameters) = @_;
foreach my $operation (@{$interface->operations}, @{$interface->anonymousOperations}) {
my $specials = $operation->specials;
my $specialExists = grep { $_ eq $special } @$specials;
my $arguments = $operation->arguments;
if ($specialExists and scalar(@$arguments) == $numberOfParameters and $arguments->[0]->type->name eq $firstParameterType) {
return $operation;
}
}
return 0;
}
sub IsGlobalInterface
{
my $interface = shift;
return $interface->extendedAttributes->{Global};
}
sub AttributeShouldBeOnInstance
{
my $interface = shift;
my $attribute = shift;
return 1 if IsGlobalInterface($interface);
return 1 if $codeGenerator->IsConstructorType($attribute->type);
# [LegacyUnforgeable] attributes should be on the instance.
# https://heycam.github.io/webidl/#LegacyUnforgeable
return 1 if IsLegacyUnforgeable($interface, $attribute);
if ($interface->extendedAttributes->{CheckSecurity}) {
return 0 if $attribute->extendedAttributes->{DoNotCheckSecurity};
return 0 if $attribute->extendedAttributes->{DoNotCheckSecurityOnGetter};
return 1;
}
return 0;
}
sub IsAlwaysExposedOnInterface
{
my ($interfaceExposures, $contextExposures) = @_;
my %contextExposureSet = ();
if (ref($contextExposures) eq "ARRAY") {
foreach my $contextExposure (@$contextExposures) {
$contextExposureSet{$contextExposure} = 1;
}
} else {
$contextExposureSet{$contextExposures} = 1;
}
if (ref($interfaceExposures) ne "ARRAY") {
$interfaceExposures = [$interfaceExposures];
}
foreach my $interfaceExposure (@$interfaceExposures) {
return 0 unless exists $contextExposureSet{$interfaceExposure};
}
return 1;
}
sub NeedsRuntimeCheck
{
my ($interface, $context) = @_;
if ($context->extendedAttributes->{Exposed}) {
return 1 if !IsAlwaysExposedOnInterface($interface->extendedAttributes->{Exposed}, $context->extendedAttributes->{Exposed});
}
return $context->extendedAttributes->{EnabledAtRuntime}
|| $context->extendedAttributes->{EnabledForContext}
|| $context->extendedAttributes->{EnabledForWorld}
|| $context->extendedAttributes->{EnabledBySetting}
|| $context->extendedAttributes->{EnabledByQuirk}
|| $context->extendedAttributes->{DisabledByQuirk}
|| $context->extendedAttributes->{SecureContext}
|| $context->extendedAttributes->{CustomEnabled};
}
sub NeedsRuntimeReadWriteCheck
{
my ($interface, $context) = @_;
return $context->extendedAttributes->{RuntimeConditionallyReadWrite}
|| $context->extendedAttributes->{SettingsConditionallyReadWrite}
}
# https://heycam.github.io/webidl/#es-operations
sub OperationShouldBeOnInstance
{
my ($interface, $operation) = @_;
return 1 if IsGlobalInterface($interface);
# [LegacyUnforgeable] operations should be on the instance. https://heycam.github.io/webidl/#LegacyUnforgeable
if (IsLegacyUnforgeable($interface, $operation)) {
assert("The bindings generator does not support putting runtime-enabled operations on the instance yet (except for global objects):[" . $interface->type->name . "::" . $operation->name . "]") if NeedsRuntimeCheck($interface, $operation);
return 1;
}
return 0;
}
sub GetOperationReturnedArgumentName
{
my ($operation) = @_;
my $argumentIndex = 0;
foreach my $argument (@{$operation->arguments}) {
return "argument${argumentIndex}" if $argument->extendedAttributes->{ReturnValue};
$argumentIndex++;
}
return undef;
}
sub IsAcceleratedDOMAttribute
{
my ($interface, $attribute) = @_;
# If we use CustomGetterSetter in IDL code generator we cannot skip type check.
return 0 if NeedsRuntimeCheck($interface, $attribute) and AttributeShouldBeOnInstance($interface, $attribute);
return 0 if $attribute->extendedAttributes->{PrivateIdentifier} and AttributeShouldBeOnInstance($interface, $attribute);
# If the interface has special logic for casting we cannot hoist type check to JSC.
return 0 if IsDOMGlobalObject($interface);
return 0 if $attribute->isStatic;
return 0 if $attribute->extendedAttributes->{ForwardToMapLike};
return 0 if $attribute->extendedAttributes->{ForwardToSetLike};
return 0 if $codeGenerator->IsConstructorType($attribute->type);
return 0 if IsJSBuiltin($interface, $attribute);
return 0 if $attribute->extendedAttributes->{LegacyLenientThis};
return 0 if $codeGenerator->IsPromiseType($attribute->type);
return 0 if $attribute->extendedAttributes->{DOMJIT};
return 1;
}
sub GetJSCAttributesForAttribute
{
my $interface = shift;
my $attribute = shift;
my @specials = ();
push(@specials, "JSC::PropertyAttribute::DontDelete") if IsLegacyUnforgeable($interface, $attribute);
# As per Web IDL specification, constructor properties on the ECMAScript global object should not be enumerable.
my $isGlobalConstructor = $codeGenerator->IsConstructorType($attribute->type);
push(@specials, "JSC::PropertyAttribute::DontEnum") if ($attribute->extendedAttributes->{NotEnumerable} || $isGlobalConstructor);
push(@specials, "JSC::PropertyAttribute::ReadOnly") if IsReadonly($attribute);
push(@specials, "JSC::PropertyAttribute::CustomAccessor") unless $isGlobalConstructor or IsJSBuiltin($interface, $attribute);
push(@specials, "JSC::PropertyAttribute::DOMAttribute") if IsAcceleratedDOMAttribute($interface, $attribute);
push(@specials, "JSC::PropertyAttribute::DOMJITAttribute") if $attribute->extendedAttributes->{DOMJIT};
push(@specials, "JSC::PropertyAttribute::Accessor | JSC::PropertyAttribute::Builtin") if IsJSBuiltin($interface, $attribute);
return "static_cast<unsigned>(" . ((@specials > 0) ? join(" | ", @specials) : "0") . ")";
}
sub GetIndexedGetterOperation
{
my $interface = shift;
return GetSpecialAccessorOperationForType($interface, "getter", "unsigned long", 1);
}
sub GetIndexedSetterOperation
{
my $interface = shift;
return GetSpecialAccessorOperationForType($interface, "setter", "unsigned long", 2);
}
sub GetNamedGetterOperation
{
my $interface = shift;
return GetSpecialAccessorOperationForType($interface, "getter", "DOMString", 1);
}
sub GetNamedSetterOperation
{
my $interface = shift;
return GetSpecialAccessorOperationForType($interface, "setter", "DOMString", 2);
}
sub GetNamedDeleterOperation
{
my $interface = shift;
return GetSpecialAccessorOperationForType($interface, "deleter", "DOMString", 1);
}
sub InstanceOperationCount
{
my $interface = shift;
my $count = 0;
foreach my $operation (@{$interface->operations}) {
$count++ if OperationShouldBeOnInstance($interface, $operation);
}
return $count;
}
sub PrototypeOperationCount
{
my $interface = shift;
my $count = 0;
foreach my $operation (@{$interface->operations}) {
$count++ if !$operation->isStatic && !OperationShouldBeOnInstance($interface, $operation);
}
return $count;
}
sub InstancePropertyCount
{
my $interface = shift;
my $count = 0;
foreach my $attribute (@{$interface->attributes}) {
$count++ if AttributeShouldBeOnInstance($interface, $attribute);
}
$count += InstanceOperationCount($interface);
return $count;
}
sub PrototypePropertyCount
{
my $interface = shift;
my $count = 0;
foreach my $attribute (@{$interface->attributes}) {
$count++ if !AttributeShouldBeOnInstance($interface, $attribute);
}
$count += PrototypeOperationCount($interface);
$count++ if NeedsConstructorProperty($interface);
return $count;
}
sub InstanceOverridesGetOwnPropertySlot
{
my $interface = shift;
return $interface->extendedAttributes->{CustomGetOwnPropertySlot}
|| $interface->extendedAttributes->{Plugin}
|| GetIndexedGetterOperation($interface)
|| GetNamedGetterOperation($interface);
}
sub InstanceOverridesGetOwnPropertyNames
{
my $interface = shift;
return $interface->extendedAttributes->{CustomGetOwnPropertyNames}
|| GetIndexedGetterOperation($interface)
|| GetNamedGetterOperation($interface);
}
sub InstanceOverridesPut
{
my $interface = shift;
return $interface->extendedAttributes->{CustomPut}
|| $interface->extendedAttributes->{Plugin}
|| GetIndexedSetterOperation($interface)
|| GetNamedSetterOperation($interface);
}
sub InstanceOverridesDefineOwnProperty
{
my $interface = shift;
return $interface->extendedAttributes->{CustomDefineOwnProperty}
|| GetIndexedSetterOperation($interface)
|| GetNamedSetterOperation($interface);
}
sub InstanceOverridesDeleteProperty
{
my $interface = shift;
return $interface->extendedAttributes->{CustomDeleteProperty}
|| GetNamedDeleterOperation($interface);
}
sub PrototypeHasStaticPropertyTable
{
my $interface = shift;
my $numConstants = @{$interface->constants};
return $numConstants > 0 || PrototypePropertyCount($interface) > 0;
}
sub InstanceNeedsVisitChildren
{
my $interface = shift;
foreach my $attribute (@{$interface->attributes}) {
return 1 if $attribute->extendedAttributes->{CachedAttribute};
}
return 1 if $interface->extendedAttributes->{JSCustomMarkFunction};
return 1 if $interface->extendedAttributes->{GenerateAddOpaqueRoot};
return 1 if $interface->extendedAttributes->{Plugin};
return 1 if $interface->extendedAttributes->{ReportExtraMemoryCost};
return 0;
}
sub InstanceNeedsEstimatedSize
{
my $interface = shift;
return $interface->extendedAttributes->{ReportExtraMemoryCost};
}
sub GetImplClassName
{
my $interface = shift;
return $interface->type->name;
}
sub IsClassNameWordBoundary
{
my ($name, $i) = @_;
# Interpret negative numbers as distance from end of string, just as the substr function does.
$i += length($name) if $i < 0;
return 0 if $i < 0;
return 1 if $i == 0;
return 1 if $i == length($name);
return 0 if $i > length($name);
my $checkString = substr($name, $i - 1);
return $checkString =~ /^[^A-Z][A-Z]/ || $checkString =~ /^[A-Z][A-Z][^A-Z]/;
}
sub IsPrefixRemovable
{
my ($class, $name, $i) = @_;
return IsClassNameWordBoundary($name, $i)
&& (IsClassNameWordBoundary($class, $i) && substr($class, 0, $i) eq substr($name, 0, $i)
|| IsClassNameWordBoundary($class, -$i) && substr($class, -$i) eq substr($name, 0, $i));
}
sub GetNestedClassName
{
my ($interface, $name) = @_;
my $class = GetImplClassName($interface);
my $member = $codeGenerator->WK_ucfirst($name);
# Since the enumeration name will be nested in the class name's namespace, remove any words
# that happen to match the start or end of the class name. If an enumeration is named TrackType or
# TextTrackType, and the class is named TextTrack, then we will get a name like TextTrack::Type.
my $memberLength = length($member);
my $longestPrefixLength = 0;
if ($member =~ /^[A-Z]./) {
for (my $i = 2; $i < $memberLength - 1; $i++) {
$longestPrefixLength = $i if IsPrefixRemovable($class, $member, $i);
}
}
$member = substr($member, $longestPrefixLength);
return "${class}::$member";
}
sub GetEnumerationClassName
{
my ($type, $interface) = @_;
assert("Not a type") if ref($type) ne "IDLType";
if ($codeGenerator->HasEnumImplementationNameOverride($type)) {
return $codeGenerator->GetEnumImplementationNameOverride($type);
}
my $name = $type->name;
return $name if $codeGenerator->IsExternalEnumType($type);
return $name unless defined($interface);
return GetNestedClassName($interface, $name);
}
sub GetEnumerationValueName
{
my ($name) = @_;
return "EmptyString" if $name eq "";
$name = join("", map { $codeGenerator->WK_ucfirst($_) } split("-", $name));
$name = "_$name" if $name =~ /^\d/;
return $name;
}
sub GenerateEnumerationHeader
{
my ($object, $enumeration, $className) = @_;
# - Add default header template and header protection.
push(@headerContentHeader, GenerateHeaderContentHeader($enumeration));
$headerIncludes{"${className}.h"} = 1;
push(@headerContent, "\nnamespace WebCore {\n\n");
push(@headerContent, GenerateEnumerationHeaderContent($enumeration, $className));
push(@headerContent, "} // namespace WebCore\n");
my $conditionalString = $codeGenerator->GenerateConditionalString($enumeration);
push(@headerContent, "\n#endif // ${conditionalString}\n") if $conditionalString;
}
sub GenerateEnumerationImplementation
{
my ($object, $enumeration, $className) = @_;
# - Add default header template
push(@implContentHeader, GenerateImplementationContentHeader($enumeration));
push(@implContent, "\n\nnamespace WebCore {\n");
push(@implContent, "using namespace JSC;\n\n");
push(@implContent, GenerateEnumerationImplementationContent($enumeration, $className));
push(@implContent, "} // namespace WebCore\n");
my $conditionalString = $codeGenerator->GenerateConditionalString($enumeration);
push(@implContent, "\n#endif // ${conditionalString}\n") if $conditionalString;
}
sub GenerateEnumerationImplementationContent
{
my ($enumeration, $className, $interface, $conditionalString) = @_;
# FIXME: A little ugly to have this be a side effect instead of a return value.
AddToImplIncludes("<JavaScriptCore/JSString.h>");
AddToImplIncludes("<JavaScriptCore/JSCInlines.h>");
AddToImplIncludes("JSDOMConvertEnumeration.h");
my $result = "";
$result .= "#if ${conditionalString}\n\n" if $conditionalString;
$result .= "String convertEnumerationToString($className enumerationValue)\n";
$result .= "{\n";
AddToImplIncludes("<wtf/NeverDestroyed.h>");
$result .= " static const NeverDestroyed<String> values[] = {\n";
foreach my $value (@{$enumeration->values}) {
if ($value eq "") {
$result .= " emptyString(),\n";
} else {
$result .= " MAKE_STATIC_STRING_IMPL(\"$value\"),\n";
}
}
$result .= " };\n";
my $index = 0;
foreach my $value (@{$enumeration->values}) {
my $enumerationValueName = GetEnumerationValueName($value);
$result .= " static_assert(static_cast<size_t>(${className}::$enumerationValueName) == $index, \"${className}::$enumerationValueName is not $index as expected\");\n";
$index++;
}
$result .= " ASSERT(static_cast<size_t>(enumerationValue) < WTF_ARRAY_LENGTH(values));\n";
$result .= " return values[static_cast<size_t>(enumerationValue)];\n";
$result .= "}\n\n";
# FIXME: Change to take VM& instead of JSGlobalObject*.
$result .= "template<> JSString* convertEnumerationToJS(JSGlobalObject& lexicalGlobalObject, $className enumerationValue)\n";
$result .= "{\n";
$result .= " return jsStringWithCache(lexicalGlobalObject.vm(), convertEnumerationToString(enumerationValue));\n";
$result .= "}\n\n";
# FIXME: Change to take VM& instead of JSGlobalObject&.
# FIXME: Consider using toStringOrNull to make exception checking faster.
# FIXME: Consider finding a more efficient way to match against all the strings quickly.
$result .= "template<> std::optional<$className> parseEnumeration<$className>(JSGlobalObject& lexicalGlobalObject, JSValue value)\n";
$result .= "{\n";
$result .= " auto stringValue = value.toWTFString(&lexicalGlobalObject);\n";
foreach my $value (@{$enumeration->values}) {
my $enumerationValueName = GetEnumerationValueName($value);
if ($value eq "") {
$result .= " if (stringValue.isEmpty())\n";
} else {
$result .= " if (stringValue == \"$value\")\n";
}
$result .= " return ${className}::${enumerationValueName};\n";
}
$result .= " return std::nullopt;\n";
$result .= "}\n\n";
$result .= "template<> const char* expectedEnumerationValues<$className>()\n";
$result .= "{\n";
$result .= " return \"\\\"" . join ("\\\", \\\"", @{$enumeration->values}) . "\\\"\";\n";
$result .= "}\n\n";
$result .= "#endif\n\n" if $conditionalString;
return $result;
}
sub GenerateEnumerationsImplementationContent
{
my ($interface, $enumerations) = @_;
return "" unless @$enumerations;
my $result = "";
foreach my $enumeration (@$enumerations) {
my $className = GetEnumerationClassName($enumeration->type, $interface);
my $conditionalString = $codeGenerator->GenerateConditionalString($enumeration);
$result .= GenerateEnumerationImplementationContent($enumeration, $className, $interface, $conditionalString);
}
return $result;
}
sub GenerateEnumerationHeaderContent
{
my ($enumeration, $className, $conditionalString) = @_;
$headerIncludes{"JSDOMConvertEnumeration.h"} = 1;
my $result = "";
$result .= "#if ${conditionalString}\n\n" if $conditionalString;
my $exportMacro = GetExportMacroForJSClass($enumeration);
$result .= "${exportMacro}String convertEnumerationToString($className);\n";
$result .= "template<> ${exportMacro}JSC::JSString* convertEnumerationToJS(JSC::JSGlobalObject&, $className);\n\n";
$result .= "template<> ${exportMacro}std::optional<$className> parseEnumeration<$className>(JSC::JSGlobalObject&, JSC::JSValue);\n";
$result .= "template<> ${exportMacro}const char* expectedEnumerationValues<$className>();\n\n";
$result .= "#endif\n\n" if $conditionalString;
return $result;
}
sub GenerateEnumerationsHeaderContent
{
my ($interface, $enumerations) = @_;
return "" unless @$enumerations;
# FIXME: Could optimize this to only generate the parts of each enumeration that are actually
# used, which would require iterating over everything in the interface.
my $result = "";
foreach my $enumeration (@$enumerations) {
my $className = GetEnumerationClassName($enumeration->type, $interface);
my $conditionalString = $codeGenerator->GenerateConditionalString($enumeration);
$result .= GenerateEnumerationHeaderContent($enumeration, $className, $conditionalString);
}
return $result;
}
sub GetDictionaryClassName
{
my ($type, $interface) = @_;
if ($codeGenerator->HasDictionaryImplementationNameOverride($type)) {
return $codeGenerator->GetDictionaryImplementationNameOverride($type);
}
my $name = $type->name;
return $name if $codeGenerator->IsExternalDictionaryType($type);
return $name unless defined($interface);
return GetNestedClassName($interface, $name);
}
sub GenerateDefaultValue
{
my ($typeScope, $context, $type, $defaultValue) = @_;
if ($defaultValue eq "null") {
if ($type->isUnion) {
return "std::nullopt" if $type->isNullable;
my $IDLType = GetIDLType($typeScope, $type);
return "convert<${IDLType}>(lexicalGlobalObject, jsNull());";
}
return "jsNull()" if $type->name eq "any";
return "nullptr" if $codeGenerator->IsWrapperType($type) || $codeGenerator->IsBufferSourceType($type);
if ($codeGenerator->IsStringType($type)) {
my $useAtomString = $type->extendedAttributes->{AtomString};
return $useAtomString ? "nullAtom()" : "String()";
}
return "std::nullopt";
}
if ($defaultValue eq "[]") {
my $IDLType = GetIDLType($typeScope, $type);
return "Converter<${IDLType}>::ReturnType{ }";
}
return "jsUndefined()" if $defaultValue eq "undefined";
return "PNaN" if $defaultValue eq "NaN";
if (substr($defaultValue, 0, 1) eq "\"") {
# Default value is a quoted string so the type should be a DOMString or an enumeration.
if ($type->isUnion) {
foreach my $memberType (GetFlattenedMemberTypes($type)) {
if ($codeGenerator->IsStringType($memberType) || $codeGenerator->IsEnumType($memberType)) {
$type = $memberType;
last;
}
}
}
if ($codeGenerator->IsStringType($type)) {
my $useAtomString = $type->extendedAttributes->{AtomString};
if ($defaultValue eq "\"\"") {
return $useAtomString ? "emptyAtom()" : "emptyString()";
} else {
return $useAtomString ? "AtomString(${defaultValue}, AtomString::ConstructFromLiteral)" : "${defaultValue}_s";
}
}
if ($codeGenerator->IsEnumType($type)) {
my $className = GetEnumerationClassName($type, $typeScope);
my $enumerationValueName = GetEnumerationValueName(substr($defaultValue, 1, -1));
return $className . "::" . $enumerationValueName;
}
}
return $defaultValue;
}
sub GenerateDictionaryHeaderContent
{
my ($dictionary, $className, $conditionalString) = @_;
$headerIncludes{"JSDOMConvertDictionary.h"} = 1;
my $exportMacro = GetExportMacroForJSClass($dictionary);
my $result = "";
$result .= "#if ${conditionalString}\n\n" if $conditionalString;
$result .= "template<> ${exportMacro}${className} convertDictionary<${className}>(JSC::JSGlobalObject&, JSC::JSValue);\n\n";
if ($dictionary->extendedAttributes->{JSGenerateToJSObject}) {
$result .= "${exportMacro}JSC::JSObject* convertDictionaryToJS(JSC::JSGlobalObject&, JSDOMGlobalObject&, const ${className}&);\n\n";
}
$result .= "#endif\n\n" if $conditionalString;
return $result;
}
sub GenerateDictionariesHeaderContent
{
my ($typeScope, $allDictionaries) = @_;
return "" unless @$allDictionaries;
my $result = "";
foreach my $dictionary (@$allDictionaries) {
$headerIncludes{$typeScope->type->name . ".h"} = 1 if $typeScope;
my $className = GetDictionaryClassName($dictionary->type, $typeScope);
my $conditionalString = $codeGenerator->GenerateConditionalString($dictionary);
$result .= GenerateDictionaryHeaderContent($dictionary, $className, $conditionalString);
}
return $result;
}
sub GenerateDictionaryImplementationContent
{
my ($dictionary, $className, $interface) = @_;
my $result = "";
my $name = $dictionary->type->name;
my $typeScope = $interface || $dictionary;
my $conditional = $dictionary->extendedAttributes->{Conditional};
if ($conditional) {
my $conditionalString = $codeGenerator->GenerateConditionalStringFromAttributeValue($conditional);
$result .= "#if ${conditionalString}\n\n";
}
# FIXME: A little ugly to have this be a side effect instead of a return value.
AddToImplIncludes("<JavaScriptCore/JSCInlines.h>");
AddToImplIncludes("JSDOMConvertDictionary.h");
# https://heycam.github.io/webidl/#es-dictionary
$result .= "template<> $className convertDictionary<$className>(JSGlobalObject& lexicalGlobalObject, JSValue value)\n";
$result .= "{\n";
$result .= " VM& vm = JSC::getVM(&lexicalGlobalObject);\n";
$result .= " auto throwScope = DECLARE_THROW_SCOPE(vm);\n";
$result .= " bool isNullOrUndefined = value.isUndefinedOrNull();\n";
$result .= " auto* object = isNullOrUndefined ? nullptr : value.getObject();\n";
# 1. If Type(V) is not Undefined, Null or Object, then throw a TypeError.
$result .= " if (UNLIKELY(!isNullOrUndefined && !object)) {\n";
$result .= " throwTypeError(&lexicalGlobalObject, throwScope);\n";
$result .= " return { };\n";
$result .= " }\n";
# 2. Let dict be an empty dictionary value of type D; every dictionary member is initially considered to be not present.
# 3. Let dictionaries be a list consisting of D and all of Ds inherited dictionaries, in order from least to most derived.
my @dictionaries;
push(@dictionaries, $dictionary);
my $parentType = $dictionary->parentType;
while (defined($parentType)) {
my $parentDictionary = $codeGenerator->GetDictionaryByType($parentType);
assert("Unable to find definition for dictionary named '" . $parentType->name . "'!") unless defined($parentDictionary);
unshift(@dictionaries, $parentDictionary);
$parentType = $parentDictionary->parentType;
}
my $arguments = "";
my $comma = "";
$result .= " $className result;\n";
# 4. For each dictionary dictionary in dictionaries, in order:
foreach my $dictionary (@dictionaries) {
# For each dictionary member member declared on dictionary, in lexicographical order:
my @sortedMembers = sort { $a->name cmp $b->name } @{$dictionary->members};
foreach my $member (@sortedMembers) {
$member->default("undefined") if $member->type->name eq "any" and !defined($member->default); # Use undefined as default value for member of type 'any' unless specified otherwise.
my $conditional = $member->extendedAttributes->{Conditional};
my $type = $member->type;
AddToImplIncludesForIDLType($type, $conditional);
if ($conditional) {
my $conditionalString = $codeGenerator->GenerateConditionalStringFromAttributeValue($conditional);
$result .= "#if ${conditionalString}\n";
}
my $needsRuntimeCheck = NeedsRuntimeCheck($dictionary, $member);
my $indent = "";
if ($needsRuntimeCheck) {
my $runtimeEnableConditionalString = GenerateRuntimeEnableConditionalString($dictionary, $member, "&lexicalGlobalObject");
$result .= " if (${runtimeEnableConditionalString}) {\n";
$indent = " ";
}
# 4.1. Let key be the identifier of member.
my $key = $member->name;
my $implementedAsKey = $member->extendedAttributes->{ImplementedAs} || $key;
# 4.2. Let value be an ECMAScript value, depending on Type(V):
$result .= "${indent} JSValue ${key}Value;\n";
$result .= "${indent} if (isNullOrUndefined)\n";
$result .= "${indent} ${key}Value = jsUndefined();\n";
$result .= "${indent} else {\n";
$result .= "${indent} ${key}Value = object->get(&lexicalGlobalObject, Identifier::fromString(vm, \"${key}\"));\n";
$result .= "${indent} RETURN_IF_EXCEPTION(throwScope, { });\n";
$result .= "${indent} }\n";
my $IDLType = GetIDLType($typeScope, $type);
# 4.3. If value is not undefined, then:
$result .= "${indent} if (!${key}Value.isUndefined()) {\n";
my $nativeValue = JSValueToNative($typeScope, $member, "${key}Value", $member->extendedAttributes->{Conditional}, "&lexicalGlobalObject", "lexicalGlobalObject", "", "*jsCast<JSDOMGlobalObject*>(&lexicalGlobalObject)");
$result .= "${indent} result.$implementedAsKey = $nativeValue;\n";
$result .= "${indent} RETURN_IF_EXCEPTION(throwScope, { });\n";
# Value is undefined.
# 4.4. Otherwise, if value is undefined but the dictionary member has a default value, then:
if (!$member->isRequired && defined $member->default) {
$result .= "${indent} } else\n";
$result .= "${indent} result.$implementedAsKey = " . GenerateDefaultValue($typeScope, $member, $member->type, $member->default) . ";\n";
} elsif ($member->isRequired) {
# 4.5. Otherwise, if value is undefined and the dictionary member is a required dictionary member, then throw a TypeError.
$result .= "${indent} } else {\n";
$result .= "${indent} throwRequiredMemberTypeError(lexicalGlobalObject, throwScope, \"". $member->name ."\", \"$name\", \"". GetTypeNameForDisplayInException($type) ."\");\n";
$result .= "${indent} return { };\n";
$result .= "${indent} }\n";
} else {
$result .= "${indent} }\n";
}
if ($needsRuntimeCheck) {
$result .= " }\n";
}
$result .= "#endif\n" if $conditional;
}
}
# 5. Return dict.
$result .= " return result;\n";
$result .= "}\n\n";
if ($dictionary->extendedAttributes->{JSGenerateToJSObject}) {
AddToImplIncludes("JSDOMGlobalObject.h");
AddToImplIncludes("<JavaScriptCore/ObjectConstructor.h>");
my $hasUnconditionalMember = 0;
$result .= "JSC::JSObject* convertDictionaryToJS(JSC::JSGlobalObject& lexicalGlobalObject, JSDOMGlobalObject& globalObject, const ${className}& dictionary)\n";
$result .= "{\n";
$result .= " auto& vm = JSC::getVM(&lexicalGlobalObject);\n";
$result .= " auto throwScope = DECLARE_THROW_SCOPE(vm);\n\n";
# 1. Let O be ! ObjectCreate(%ObjectPrototype%).
$result .= " auto result = constructEmptyObject(&lexicalGlobalObject, globalObject.objectPrototype());\n\n";
# 2. Let dictionaries be a list consisting of D and all of Ds inherited dictionaries,
# in order from least to most derived.
# NOTE: This was done above.
# 3. For each dictionary dictionary in dictionaries, in order:
foreach my $dictionary (@dictionaries) {
# 3.1. For each dictionary member member declared on dictionary, in lexicographical order:
my @sortedMembers = sort { $a->name cmp $b->name } @{$dictionary->members};
foreach my $member (@sortedMembers) {
my $key = $member->name;
my $implementedAsKey = $member->extendedAttributes->{ImplementedAs} || $key;
my $valueExpression = "dictionary.${implementedAsKey}";
my $conditional = $member->extendedAttributes->{Conditional};
if ($conditional) {
my $conditionalString = $codeGenerator->GenerateConditionalStringFromAttributeValue($conditional);
$result .= "#if ${conditionalString}\n";
} else {
$hasUnconditionalMember = 1;
}
# 1. Let key be the identifier of member.
# 2. If the dictionary member named key is present in V, then:
# 1. Let idlValue be the value of member on V.
# 2. Let value be the result of converting idlValue to an ECMAScript value.
# 3. Perform ! CreateDataProperty(O, key, value).
my $needsRuntimeCheck = NeedsRuntimeCheck($dictionary, $member);
my $indent = "";
if ($needsRuntimeCheck) {
my $runtimeEnableConditionalString = GenerateRuntimeEnableConditionalString($dictionary, $member, "&globalObject");
$result .= " if (${runtimeEnableConditionalString}) {\n";
$indent = " ";
}
if (!$member->isRequired && not defined $member->default) {
my $IDLType = GetIDLType($typeScope, $member->type);
my $conversionExpression = NativeToJSValueUsingReferences($member, $typeScope, "${IDLType}::extractValueFromNullable(${valueExpression})", "globalObject");
$result .= "${indent} if (!${IDLType}::isNullValue(${valueExpression})) {\n";
$result .= "${indent} auto ${key}Value = ${conversionExpression};\n";
$result .= "${indent} RETURN_IF_EXCEPTION(throwScope, { });\n";
$result .= "${indent} result->putDirect(vm, JSC::Identifier::fromString(vm, \"${key}\"), ${key}Value);\n";
$result .= "${indent} }\n";
} else {
my $conversionExpression = NativeToJSValueUsingReferences($member, $typeScope, $valueExpression, "globalObject");
$result .= "${indent} auto ${key}Value = ${conversionExpression};\n";
$result .= "${indent} RETURN_IF_EXCEPTION(throwScope, { });\n";
$result .= "${indent} result->putDirect(vm, JSC::Identifier::fromString(vm, \"${key}\"), ${key}Value);\n";
}
if ($needsRuntimeCheck) {
$result .= " }\n";
}
$result .= "#endif\n" if $conditional;
}
}
if (!$hasUnconditionalMember) {
$result .= " UNUSED_PARAM(dictionary);\n";
$result .= " UNUSED_VARIABLE(throwScope);\n\n";
}
$result .= " return result;\n";
$result .= "}\n\n";
}
$result .= "#endif\n\n" if $conditional;
return $result;
}
sub GenerateDictionariesImplementationContent
{
my ($typeScope, $allDictionaries) = @_;
my $result = "";
foreach my $dictionary (@$allDictionaries) {
my $className = GetDictionaryClassName($dictionary->type, $typeScope);
$result .= GenerateDictionaryImplementationContent($dictionary, $className, $typeScope);
}
return $result;
}
sub GetJSTypeForNode
{
my ($interface) = @_;
if ($codeGenerator->InheritsInterface($interface, "Document")) {
return "JSDocumentWrapperType";
}
if ($codeGenerator->InheritsInterface($interface, "DocumentFragment")) {
return "JSDocumentFragmentNodeType";
}
if ($codeGenerator->InheritsInterface($interface, "DocumentType")) {
return "JSDocumentTypeNodeType";
}
if ($codeGenerator->InheritsInterface($interface, "ProcessingInstruction")) {
return "JSProcessingInstructionNodeType";
}
if ($codeGenerator->InheritsInterface($interface, "CDATASection")) {
return "JSCDATASectionNodeType";
}
if ($codeGenerator->InheritsInterface($interface, "Attr")) {
return "JSAttrNodeType";
}
if ($codeGenerator->InheritsInterface($interface, "Comment")) {
return "JSCommentNodeType";
}
if ($codeGenerator->InheritsInterface($interface, "Text")) {
return "JSTextNodeType";
}
if ($codeGenerator->InheritsInterface($interface, "Element")) {
return "JSElementType";
}
return "JSNodeType";
}
sub GenerateHeader
{
my ($object, $interface, $enumerations, $dictionaries) = @_;
my $interfaceName = $interface->type->name;
my $className = "JS$interfaceName";
my %structureFlags = ();
my $hasParent = $interface->parentType || $interface->extendedAttributes->{JSLegacyParent};
my $parentClassName = GetParentClassName($interface);
my $needsVisitChildren = InstanceNeedsVisitChildren($interface);
# - Add default header template and header protection
push(@headerContentHeader, GenerateHeaderContentHeader($interface));
if ($hasParent) {
$headerIncludes{"$parentClassName.h"} = 1;
} else {
$headerIncludes{"JSDOMWrapper.h"} = 1;
if ($interface->extendedAttributes->{Exception}) {
$headerIncludes{"<JavaScriptCore/ErrorPrototype.h>"} = 1;
}
}
$headerIncludes{"SVGElement.h"} = 1 if $className =~ /^JSSVG/;
my $implType = GetImplClassName($interface);
my $numConstants = @{$interface->constants};
my $numAttributes = @{$interface->attributes};
my $numOperations = @{$interface->operations};
push(@headerContent, "\nnamespace WebCore {\n\n");
if ($codeGenerator->IsSVGAnimatedType($interface->type)) {
$headerIncludes{"SVGAnimatedPropertyImpl.h"} = 1;
} elsif ($codeGenerator->IsSVGPathSegType($interface->type)) {
$headerIncludes{"SVGPathSegImpl.h"} = 1;
} else {
$headerIncludes{"$interfaceName.h"} = 1 if $hasParent && $interface->extendedAttributes->{JSGenerateToNativeObject};
# Implementation class forward declaration
if (IsDOMGlobalObject($interface)) {
AddClassForwardIfNeeded($interface->type);
}
}
push(@headerContent, "class JSWindowProxy;\n\n") if $interfaceName eq "DOMWindow" or $interfaceName eq "RemoteDOMWindow";
my $exportMacro = GetExportMacroForJSClass($interface);
# Class declaration
push(@headerContent, "class $exportMacro$className : public $parentClassName {\n");
# Static create methods
push(@headerContent, "public:\n");
push(@headerContent, " using Base = $parentClassName;\n");
push(@headerContent, " using DOMWrapped = $implType;\n") if $hasParent;
if ($interfaceName eq "DOMWindow" || $interfaceName eq "RemoteDOMWindow") {
push(@headerContent, " static $className* create(JSC::VM& vm, JSC::Structure* structure, Ref<$implType>&& impl, JSWindowProxy* proxy)\n");
push(@headerContent, " {\n");
push(@headerContent, " $className* ptr = new (NotNull, JSC::allocateCell<$className>(vm.heap)) ${className}(vm, structure, WTFMove(impl), proxy);\n");
push(@headerContent, " ptr->finishCreation(vm, proxy);\n");
push(@headerContent, " return ptr;\n");
push(@headerContent, " }\n\n");
} elsif ($codeGenerator->InheritsInterface($interface, "WorkerGlobalScope") || $codeGenerator->InheritsInterface($interface, "WorkletGlobalScope")) {
push(@headerContent, " static $className* create(JSC::VM& vm, JSC::Structure* structure, Ref<$implType>&& impl, JSC::JSProxy* proxy)\n");
push(@headerContent, " {\n");
push(@headerContent, " $className* ptr = new (NotNull, JSC::allocateCell<$className>(vm.heap)) ${className}(vm, structure, WTFMove(impl));\n");
push(@headerContent, " ptr->finishCreation(vm, proxy);\n");
push(@headerContent, " return ptr;\n");
push(@headerContent, " }\n\n");
} elsif ($interface->extendedAttributes->{MasqueradesAsUndefined}) {
AddIncludesForImplementationTypeInHeader($implType);
push(@headerContent, " static $className* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject, Ref<$implType>&& impl)\n");
push(@headerContent, " {\n");
push(@headerContent, " globalObject->masqueradesAsUndefinedWatchpoint()->fireAll(globalObject->vm(), \"Allocated masquerading object\");\n");
push(@headerContent, " $className* ptr = new (NotNull, JSC::allocateCell<$className>(globalObject->vm().heap)) $className(structure, *globalObject, WTFMove(impl));\n");
push(@headerContent, " ptr->finishCreation(globalObject->vm());\n");
push(@headerContent, " return ptr;\n");
push(@headerContent, " }\n\n");
} elsif (!NeedsImplementationClass($interface)) {
push(@headerContent, " static $className* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject)\n");
push(@headerContent, " {\n");
push(@headerContent, " $className* ptr = new (NotNull, JSC::allocateCell<$className>(globalObject->vm().heap)) $className(structure, *globalObject);\n");
push(@headerContent, " ptr->finishCreation(globalObject->vm());\n");
push(@headerContent, " return ptr;\n");
push(@headerContent, " }\n\n");
} else {
if (!$codeGenerator->IsSVGAnimatedType($interface->type) && !$codeGenerator->IsSVGPathSegType($interface->type)) {
AddIncludesForImplementationTypeInHeader($implType);
}
push(@headerContent, " static $className* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject, Ref<$implType>&& impl)\n");
push(@headerContent, " {\n");
push(@headerContent, " $className* ptr = new (NotNull, JSC::allocateCell<$className>(globalObject->vm().heap)) $className(structure, *globalObject, WTFMove(impl));\n");
push(@headerContent, " ptr->finishCreation(globalObject->vm());\n");
push(@headerContent, " return ptr;\n");
push(@headerContent, " }\n\n");
}
$structureFlags{"JSC::HasStaticPropertyTable"} = 1 if InstancePropertyCount($interface) > 0;
$structureFlags{"JSC::NewImpurePropertyFiresWatchpoints"} = 1 if $interface->extendedAttributes->{NewImpurePropertyFiresWatchpoints};
$structureFlags{"JSC::IsImmutablePrototypeExoticObject"} = 1 if $interface->extendedAttributes->{IsImmutablePrototypeExoticObject};
$structureFlags{"JSC::MasqueradesAsUndefined"} = 1 if $interface->extendedAttributes->{MasqueradesAsUndefined};
$structureFlags{"JSC::ImplementsHasInstance | JSC::ImplementsDefaultHasInstance"} = 1 if $interfaceName eq "DOMWindow";
# Prototype
unless (ShouldUseGlobalObjectPrototype($interface) || $interface->isNamespaceObject) {
push(@headerContent, " static JSC::JSObject* createPrototype(JSC::VM&, JSDOMGlobalObject&);\n");
push(@headerContent, " static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&);\n");
}
# JSValue to implementation type
if (ShouldGenerateToWrapped($hasParent, $interface)) {
my $export = "";
$export = "WEBCORE_EXPORT " if $interface->extendedAttributes->{ExportToWrappedFunction};
push(@headerContent, " static ${export}${implType}* toWrapped(JSC::VM&, JSC::JSValue);\n");
}
$headerTrailingIncludes{"${className}Custom.h"} = 1 if $interface->extendedAttributes->{JSCustomHeader};
my $namedGetterOperation = GetNamedGetterOperation($interface);
my $indexedGetterOperation = GetIndexedGetterOperation($interface);
# FIXME: Why doesn't this also include Indexed Getters and [CustomGetOwnPropertySlot]
if ($namedGetterOperation) {
if ($codeGenerator->InheritsExtendedAttribute($interface, "LegacyOverrideBuiltIns")) {
$structureFlags{"JSC::GetOwnPropertySlotIsImpure"} = 1;
} else {
$structureFlags{"JSC::GetOwnPropertySlotIsImpureForPropertyAbsence"} = 1;
}
}
# ClassInfo MethodTable declarations.
if (InstanceOverridesGetOwnPropertySlot($interface)) {
push(@headerContent, " static bool getOwnPropertySlot(JSC::JSObject*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&);\n");
$structureFlags{"JSC::OverridesGetOwnPropertySlot"} = 1;
push(@headerContent, " static bool getOwnPropertySlotByIndex(JSC::JSObject*, JSC::JSGlobalObject*, unsigned propertyName, JSC::PropertySlot&);\n");
$structureFlags{"JSC::InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero"} = 1;
}
if ($interface->extendedAttributes->{Plugin} || GetNamedSetterOperation($interface)) {
$structureFlags{"JSC::ProhibitsPropertyCaching"} = 1;
}
if (InstanceOverridesGetOwnPropertyNames($interface)) {
push(@headerContent, " static void getOwnPropertyNames(JSC::JSObject*, JSC::JSGlobalObject*, JSC::PropertyNameArray&, JSC::DontEnumPropertiesMode);\n");
$structureFlags{"JSC::OverridesGetOwnPropertyNames"} = 1;
}
if (InstanceOverridesPut($interface)) {
push(@headerContent, " static bool put(JSC::JSCell*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::JSValue, JSC::PutPropertySlot&);\n");
push(@headerContent, " static bool putByIndex(JSC::JSCell*, JSC::JSGlobalObject*, unsigned propertyName, JSC::JSValue, bool shouldThrow);\n");
$structureFlags{"JSC::OverridesPut"} = 1;
}
if (InstanceOverridesDefineOwnProperty($interface)) {
push(@headerContent, " static bool defineOwnProperty(JSC::JSObject*, JSC::JSGlobalObject*, JSC::PropertyName, const JSC::PropertyDescriptor&, bool shouldThrow);\n");
}
if (InstanceOverridesDeleteProperty($interface)) {
push(@headerContent, " static bool deleteProperty(JSC::JSCell*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::DeletePropertySlot&);\n");
push(@headerContent, " static bool deletePropertyByIndex(JSC::JSCell*, JSC::JSGlobalObject*, unsigned);\n");
}
if (InstanceOverridesGetCallData($interface)) {
push(@headerContent, " static JSC::CallData getCallData(JSC::JSCell*);\n\n");
$headerIncludes{"<JavaScriptCore/CallData.h>"} = 1;
$structureFlags{"JSC::OverridesGetCallData"} = 1;
}
if ($interface->extendedAttributes->{CustomGetPrototype}) {
push(@headerContent, " static JSC::JSValue getPrototype(JSC::JSObject*, JSC::JSGlobalObject*);\n");
$structureFlags{"JSC::OverridesGetPrototype"} = 1;
}
if ($interface->extendedAttributes->{CustomPreventExtensions}) {
push(@headerContent, " static bool preventExtensions(JSC::JSObject*, JSC::JSGlobalObject*);\n");
}
if (InstanceNeedsEstimatedSize($interface)) {
push(@headerContent, " static size_t estimatedSize(JSCell*, JSC::VM&);\n");
}
if (!$hasParent) {
push(@headerContent, " static void destroy(JSC::JSCell*);\n");
}
# Class info
if ($interfaceName eq "Node") {
push(@headerContent, "\n");
push(@headerContent, "protected:\n");
push(@headerContent, " static const JSC::ClassInfo s_info;\n");
push(@headerContent, "public:\n");
push(@headerContent, " static constexpr const JSC::ClassInfo* info() { return &s_info; }\n\n");
} else {
push(@headerContent, "\n");
push(@headerContent, " DECLARE_INFO;\n\n");
}
# Structure ID
push(@headerContent, " static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)\n");
push(@headerContent, " {\n");
my $indexingModeIncludingHistory = InstanceOverridesGetOwnPropertySlot($interface) ? "JSC::MayHaveIndexedAccessors" : "JSC::NonArray";
if (IsDOMGlobalObject($interface)) {
push(@headerContent, " return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::GlobalObjectType, StructureFlags), info(), $indexingModeIncludingHistory);\n");
} elsif ($codeGenerator->InheritsInterface($interface, "Node")) {
my $type = GetJSTypeForNode($interface);
push(@headerContent, " return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::JSType($type), StructureFlags), info(), $indexingModeIncludingHistory);\n");
} elsif ($codeGenerator->InheritsInterface($interface, "Event")) {
push(@headerContent, " return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::JSType(JSEventType), StructureFlags), info(), $indexingModeIncludingHistory);\n");
} else {
push(@headerContent, " return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info(), $indexingModeIncludingHistory);\n");
}
push(@headerContent, " }\n\n");
# Custom pushEventHandlerScope function
if ($interface->extendedAttributes->{CustomPushEventHandlerScope}) {
push(@headerContent, " JSC::JSScope* pushEventHandlerScope(JSC::JSGlobalObject*, JSC::JSScope*) const;\n\n");
}
# Constructor object getter
unless ($interface->extendedAttributes->{LegacyNoInterfaceObject}) {
push(@headerContent, " static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*);\n");
push(@headerContent, " static JSC::JSValue getLegacyFactoryFunction(JSC::VM&, JSC::JSGlobalObject*);\n") if $interface->extendedAttributes->{LegacyFactoryFunction};
}
my $numCustomOperations = 0;
my $numCustomAttributes = 0;
my $hasDOMJITAttributes = 0;
# Attribute and function enums
if ($numAttributes > 0) {
foreach my $attribute (@{$interface->attributes}) {
$numCustomAttributes++ if HasCustomGetter($attribute);
$numCustomAttributes++ if HasCustomSetter($attribute);
if ($attribute->extendedAttributes->{CachedAttribute}) {
my $conditionalString = $codeGenerator->GenerateConditionalString($attribute);
push(@headerContent, "#if ${conditionalString}\n") if $conditionalString;
push(@headerContent, " mutable JSC::WriteBarrier<JSC::Unknown> m_" . $attribute->name . ";\n");
$numCachedAttributes++;
push(@headerContent, "#endif\n") if $conditionalString;
}
$hasDOMJITAttributes = 1 if $attribute->extendedAttributes->{DOMJIT};
}
}
push(@headerContent, " template<typename, JSC::SubspaceAccess mode> static JSC::IsoSubspace* subspaceFor(JSC::VM& vm)\n");
push(@headerContent, " {\n");
push(@headerContent, " if constexpr (mode == JSC::SubspaceAccess::Concurrently)\n");
push(@headerContent, " return nullptr;\n");
push(@headerContent, " return subspaceForImpl(vm);\n");
push(@headerContent, " }\n");
push(@headerContent, " static JSC::IsoSubspace* subspaceForImpl(JSC::VM& vm);\n");
# visit function
if ($needsVisitChildren) {
push(@headerContent, " DECLARE_VISIT_CHILDREN;\n");
push(@headerContent, " template<typename Visitor> void visitAdditionalChildren(Visitor&);\n") if $interface->extendedAttributes->{JSCustomMarkFunction};
push(@headerContent, "\n");
if ($interface->extendedAttributes->{JSCustomMarkFunction}) {
# We assume that the logic in visitAdditionalChildren is highly volatile, and during a
# concurrent GC or in between eden GCs something may happen that would lead to this
# logic behaving differently. Since this could mark objects or add opaque roots, this
# means that after any increment of mutator resumption in a concurrent GC and at least
# once during any eden GC we need to re-execute visitAdditionalChildren on any objects
# that we had executed it on before. We do this using the DOM's own MarkingConstraint,
# which will call visitOutputConstraints on all objects in the DOM's own
# outputConstraintSubspace. visitOutputConstraints is the name JSC uses for the method
# that the GC calls to ask an object is it would like to mark anything else after the
# program resumed since the last call to visitChildren or visitOutputConstraints. Since
# this just calls visitAdditionalChildren, you usually don't have to worry about this.
push(@headerContent, " template<typename Visitor> static void visitOutputConstraints(JSCell*, Visitor&);\n");
}
}
if (NeedsImplementationClass($interface)) {
push(@headerContent, " static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);\n");
}
if ($numCustomAttributes > 0) {
push(@headerContent, "\n // Custom attributes\n");
foreach my $attribute (@{$interface->attributes}) {
my $conditionalString = $codeGenerator->GenerateConditionalString($attribute);
if (HasCustomGetter($attribute)) {
push(@headerContent, "#if ${conditionalString}\n") if $conditionalString;
my $methodName = $codeGenerator->WK_lcfirst($attribute->name);
push(@headerContent, " JSC::JSValue " . $methodName . "(JSC::JSGlobalObject&) const;\n");
push(@headerContent, "#endif\n") if $conditionalString;
}
if (HasCustomSetter($attribute) && !IsReadonly($attribute)) {
push(@headerContent, "#if ${conditionalString}\n") if $conditionalString;
push(@headerContent, " void set" . $codeGenerator->WK_ucfirst($attribute->name) . "(JSC::JSGlobalObject&, JSC::JSValue);\n");
push(@headerContent, "#endif\n") if $conditionalString;
}
}
}
foreach my $operation (@{$interface->operations}) {
$numCustomOperations++ if HasCustomMethod($operation);
}
if ($numCustomOperations > 0) {
my $inAppleCopyright = 0;
push(@headerContent, "\n // Custom functions\n");
foreach my $operation (@{$interface->operations}) {
next unless HasCustomMethod($operation);
next if $operation->{overloads} && $operation->{overloadIndex} != 1;
if ($operation->extendedAttributes->{AppleCopyright}) {
if (!$inAppleCopyright) {
push(@headerContent, $beginAppleCopyrightForHeaderFiles);
$inAppleCopyright = 1;
}
} elsif ($inAppleCopyright) {
push(@headerContent, $endAppleCopyright);
$inAppleCopyright = 0;
}
my $conditionalString = $codeGenerator->GenerateConditionalString($operation);
push(@headerContent, "#if ${conditionalString}\n") if $conditionalString;
my $functionImplementationName = $operation->extendedAttributes->{ImplementedAs} || $codeGenerator->WK_lcfirst($operation->name);
my @functionArguments = ();
push(@functionArguments, "JSC::JSGlobalObject&");
push(@functionArguments, "JSC::CallFrame&");
push(@functionArguments, "Ref<DeferredPromise>&&") if $codeGenerator->IsPromiseType($operation->type) && !$operation->extendedAttributes->{ReturnsOwnPromise};
push(@headerContent, " " . ($operation->isStatic ? "static " : "") . "JSC::JSValue " . $functionImplementationName . "(" . join(", ", @functionArguments) . ");\n");
push(@headerContent, "#endif\n") if $conditionalString;
}
push(@headerContent, $endAppleCopyright) if $inAppleCopyright;
}
if (NeedsImplementationClass($interface)) {
if ($hasParent) {
push(@headerContent, " $interfaceName& wrapped() const\n");
push(@headerContent, " {\n");
push(@headerContent, " return static_cast<$interfaceName&>(Base::wrapped());\n");
push(@headerContent, " }\n");
}
}
# structure flags
if (%structureFlags) {
push(@headerContent, "public:\n");
push(@headerContent, " static constexpr unsigned StructureFlags = Base::StructureFlags");
foreach my $structureFlag (sort (keys %structureFlags)) {
push(@headerContent, " | " . $structureFlag);
}
push(@headerContent, ";\n");
}
push(@headerContent, "protected:\n");
# Constructor
if ($interfaceName eq "DOMWindow" || $interfaceName eq "RemoteDOMWindow") {
push(@headerContent, " $className(JSC::VM&, JSC::Structure*, Ref<$implType>&&, JSWindowProxy*);\n");
} elsif ($codeGenerator->InheritsInterface($interface, "WorkerGlobalScope") || $codeGenerator->InheritsInterface($interface, "WorkletGlobalScope")) {
push(@headerContent, " $className(JSC::VM&, JSC::Structure*, Ref<$implType>&&);\n");
} elsif (!NeedsImplementationClass($interface)) {
push(@headerContent, " $className(JSC::Structure*, JSDOMGlobalObject&);\n\n");
} else {
push(@headerContent, " $className(JSC::Structure*, JSDOMGlobalObject&, Ref<$implType>&&);\n\n");
}
if ($interfaceName eq "DOMWindow" || $interfaceName eq "RemoteDOMWindow") {
push(@headerContent, " void finishCreation(JSC::VM&, JSWindowProxy*);\n");
} elsif ($codeGenerator->InheritsInterface($interface, "WorkerGlobalScope") || $codeGenerator->InheritsInterface($interface, "WorkletGlobalScope")) {
push(@headerContent, " void finishCreation(JSC::VM&, JSC::JSProxy*);\n");
} else {
push(@headerContent, " void finishCreation(JSC::VM&);\n");
}
push(@headerContent, "};\n\n");
if (ShouldGenerateWrapperOwnerCode($hasParent, $interface)) {
my $overrideDecl = "final";
if ($interfaceName eq "Node") {
push(@headerContent, "class ${exportMacro}JS${interfaceName}Owner : public JSC::WeakHandleOwner {\n");
$overrideDecl = "override";
} elsif ($codeGenerator->InheritsInterface($interface, "Node")) {
$headerIncludes{"JSNode.h"} = 1;
push(@headerContent, "class ${exportMacro}JS${interfaceName}Owner final : public JSNodeOwner {\n");
} else {
push(@headerContent, "class ${exportMacro}JS${interfaceName}Owner final : public JSC::WeakHandleOwner {\n");
}
$headerIncludes{"<wtf/NeverDestroyed.h>"} = 1;
push(@headerContent, "public:\n");
push(@headerContent, " bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::AbstractSlotVisitor&, const char**) ${overrideDecl};\n");
push(@headerContent, " void finalize(JSC::Handle<JSC::Unknown>, void* context) ${overrideDecl};\n");
push(@headerContent, "};\n");
push(@headerContent, "\n");
push(@headerContent, "inline JSC::WeakHandleOwner* wrapperOwner(DOMWrapperWorld&, $implType*)\n");
push(@headerContent, "{\n");
push(@headerContent, " static NeverDestroyed<JS${interfaceName}Owner> owner;\n");
push(@headerContent, " return &owner.get();\n");
push(@headerContent, "}\n");
push(@headerContent, "\n");
push(@headerContent, "inline void* wrapperKey($implType* wrappableObject)\n");
push(@headerContent, "{\n");
push(@headerContent, " return wrappableObject;\n");
push(@headerContent, "}\n");
push(@headerContent, "\n");
}
if (ShouldGenerateToJSDeclaration($hasParent, $interface)) {
# Node and NodeList have custom inline implementations which thus cannot be exported.
# FIXME: The special case for Node and NodeList should probably be implemented via an IDL attribute.
if ($implType eq "Node" or $implType eq "NodeList") {
push(@headerContent, "JSC::JSValue toJS(JSC::JSGlobalObject*, JSDOMGlobalObject*, $implType&);\n");
} else {
push(@headerContent, $exportMacro."JSC::JSValue toJS(JSC::JSGlobalObject*, JSDOMGlobalObject*, $implType&);\n");
}
push(@headerContent, "inline JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, $implType* impl) { return impl ? toJS(lexicalGlobalObject, globalObject, *impl) : JSC::jsNull(); }\n");
push(@headerContent, "JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject*, Ref<$implType>&&);\n");
push(@headerContent, "inline JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, RefPtr<$implType>&& impl) { return impl ? toJSNewlyCreated(lexicalGlobalObject, globalObject, impl.releaseNonNull()) : JSC::jsNull(); }\n");
}
push(@headerContent, "\n");
GeneratePrototypeDeclaration(\@headerContent, $className, $interface) if HeaderNeedsPrototypeDeclaration($interface);
# CheckJSCast Snippet function.
if ($interface->extendedAttributes->{DOMJIT}) {
$headerIncludes{"<JavaScriptCore/Snippet.h>"} = 1;
push(@headerContent, "#if ENABLE(JIT)\n");
push(@headerContent, "Ref<JSC::Snippet> checkSubClassSnippetFor${className}();\n");
push(@headerContent, "#endif\n");
}
if ($hasDOMJITAttributes) {
$headerIncludes{"<JavaScriptCore/DOMJITGetterSetter.h>"} = 1;
push(@headerContent,"// DOM JIT Attributes\n\n");
foreach my $attribute (@{$interface->attributes}) {
next unless $attribute->extendedAttributes->{DOMJIT};
assert("Only DOMJIT=Getter is supported for attributes") unless $codeGenerator->ExtendedAttributeContains($attribute->extendedAttributes->{DOMJIT}, "Getter");
my $interfaceName = $interface->type->name;
my $className = $interfaceName . $codeGenerator->WK_ucfirst($attribute->name);
my $domJITClassName = $className . "Attribute";
push(@headerContent, "#if ENABLE(JIT)\n");
push(@headerContent, "Ref<JSC::DOMJIT::CallDOMGetterSnippet> compile${domJITClassName}();\n");
push(@headerContent, "#endif\n\n");
}
}
if (HasCustomConstructor($interface)) {
push(@headerContent, "// Custom constructor\n");
push(@headerContent, "JSC::EncodedJSValue construct${className}(JSC::JSGlobalObject*, JSC::CallFrame&);\n\n");
}
if (NeedsImplementationClass($interface)) {
$headerIncludes{"JSDOMWrapper.h"} = 1;
push(@headerContent, "template<> struct JSDOMWrapperConverterTraits<${implType}> {\n");
push(@headerContent, " using WrapperClass = ${className};\n");
push(@headerContent, " using ToWrappedReturnType = ${implType}*;\n");
push(@headerContent, "};\n");
}
push(@headerContent, GenerateEnumerationsHeaderContent($interface, $enumerations));
push(@headerContent, GenerateDictionariesHeaderContent($interface, $dictionaries));
my $conditionalString = $codeGenerator->GenerateConditionalString($interface);
push(@headerContent, "\n} // namespace WebCore\n");
push(@headerContent, "\n#endif // ${conditionalString}\n") if $conditionalString;
if ($interface->extendedAttributes->{AppleCopyright}) {
push(@headerContent, "\n");
push(@headerContent, split("\r", $endAppleCopyright));
}
# - Generate dependencies.
if ($writeDependencies) {
my @ancestors;
$codeGenerator->ForAllParents($interface, sub {
my $currentInterface = shift;
push(@ancestors, $currentInterface->type->name);
}, 0);
for my $dictionary (@$dictionaries) {
my $parentType = $dictionary->parentType;
while (defined($parentType)) {
push(@ancestors, $parentType->name) if $codeGenerator->IsExternalDictionaryType($parentType);
my $parentDictionary = $codeGenerator->GetDictionaryByType($parentType);
assert("Unable to find definition for dictionary named '" . $parentType->name . "'!") unless defined($parentDictionary);
$parentType = $parentDictionary->parentType;
}
}
push(@depsContent, "$className.h : ", join(" ", map { "$_.idl" } @ancestors), "\n");
push(@depsContent, map { "$_.idl :\n" } @ancestors);
}
}
sub GeneratePropertiesHashTable
{
my ($object, $interface, $isInstance, $hashKeys, $hashSpecials, $hashValue1, $hashValue2, $conditionals, $readWriteConditionals, $runtimeEnabledOperations, $runtimeEnabledAttributes) = @_;
# FIXME: These should be functions on $interface.
my $interfaceName = $interface->type->name;
my $className = "JS$interfaceName";
# - Add all properties in a hashtable definition
my $propertyCount = $isInstance ? InstancePropertyCount($interface) : PrototypePropertyCount($interface);
if (!$isInstance && NeedsConstructorProperty($interface)) {
die if !$propertyCount;
push(@$hashKeys, "constructor");
my $getter = "js" . $interfaceName . "Constructor";
push(@$hashValue1, $getter);
push(@$hashValue2, "0");
push(@$hashSpecials, "static_cast<unsigned>(JSC::PropertyAttribute::DontEnum)");
}
return 0 if !$propertyCount;
foreach my $attribute (@{$interface->attributes}) {
next if ($attribute->isStatic);
next if AttributeShouldBeOnInstance($interface, $attribute) != $isInstance;
next if ($attribute->extendedAttributes->{PrivateIdentifier} and not $attribute->extendedAttributes->{PublicIdentifier});
# Global objects add RuntimeEnabled attributes after creation so do not add them to the static table.
if ($isInstance && NeedsRuntimeCheck($interface, $attribute)) {
$propertyCount -= 1;
next;
}
my $name = $attribute->name;
push(@$hashKeys, $name);
my $special = GetJSCAttributesForAttribute($interface, $attribute);
push(@$hashSpecials, $special);
if ($attribute->extendedAttributes->{DOMJIT}) {
push(@$hashValue1, "&DOMJITAttributeFor" . $interface->type->name . $codeGenerator->WK_ucfirst($attribute->name));
} else {
my $getter = GetAttributeGetterName($interface, $className, $attribute);
push(@$hashValue1, $getter);
}
if (IsReadonly($attribute) || $codeGenerator->IsConstructorType($attribute->type)) {
push(@$hashValue2, "0");
} else {
my $setter = GetAttributeSetterName($interface, $className, $attribute);
push(@$hashValue2, $setter);
}
my $conditional = $attribute->extendedAttributes->{Conditional};
$conditionals->{$name} = $conditional if $conditional;
my $readWriteConditional = $attribute->extendedAttributes->{ConditionallyReadWrite};
$readWriteConditionals->{$name} = $readWriteConditional if $readWriteConditional;
my $needsRuntimeCheck = NeedsRuntimeCheck($interface, $attribute);
my $needsRuntimeReadWriteCheck = NeedsRuntimeReadWriteCheck($interface, $attribute);
if ($needsRuntimeCheck && $needsRuntimeReadWriteCheck) {
die "Being both runtime enabled and runtime conditionally read-write is not yet supported (used on the '${name}' attribute of '${interfaceName}').\n";
}
if ($needsRuntimeCheck || $needsRuntimeReadWriteCheck) {
push(@$runtimeEnabledAttributes, $attribute);
}
}
foreach my $operation (@{$interface->operations}) {
next if ($operation->extendedAttributes->{PrivateIdentifier} and not $operation->extendedAttributes->{PublicIdentifier});
next if ($operation->isStatic);
next if $operation->{overloadIndex} && $operation->{overloadIndex} > 1;
next if OperationShouldBeOnInstance($interface, $operation) != $isInstance;
next if $operation->name eq "[Symbol.Iterator]";
# Global objects add RuntimeEnabled operations after creation so do not add them to the static table.
if ($isInstance && NeedsRuntimeCheck($interface, $operation)) {
$propertyCount -= 1;
next;
}
my $name = $operation->name;
push(@$hashKeys, $name);
my $functionName = GetFunctionName($interface, $className, $operation);
push(@$hashValue1, $functionName);
my $functionLength = GetFunctionLength($operation);
if ($operation->extendedAttributes->{DOMJIT}) {
push(@$hashValue2, "&DOMJITSignatureFor" . $interface->type->name . $codeGenerator->WK_ucfirst($operation->name));
} else {
push(@$hashValue2, $functionLength);
}
push(@$hashSpecials, ComputeFunctionSpecial($interface, $operation));
my $conditional = GetConditionalForOperationConsideringOverloads($operation);
$conditionals->{$name} = $conditional if $conditional;
if (NeedsRuntimeCheck($interface, $operation)) {
push(@$runtimeEnabledOperations, $operation);
}
}
return $propertyCount;
}
# This computes an effective overload set for a given operation / constructor,
# which represents the allowable invocations.This set is used as input for
# the Web IDL overload resolution algorithm.
# http://heycam.github.io/webidl/#dfn-effective-overload-set
sub ComputeEffectiveOverloadSet
{
my ($overloads) = @_;
my %allSets;
my $addTuple = sub {
my $tuple = shift;
# The Web IDL specification uses a flat set of tuples but we use a hash where the key is the
# number of parameters and the value is the set of tuples for the given number of parameters.
my $length = scalar(@{@$tuple[1]});
if (!exists($allSets{$length})) {
$allSets{$length} = [ $tuple ];
} else {
push(@{$allSets{$length}}, $tuple);
}
};
my $m = LengthOfLongestOperationParameterList($overloads);
foreach my $overload (@{$overloads}) {
my $n = @{$overload->arguments};
my @t;
my @o;
my $isVariadic = 0;
foreach my $argument (@{$overload->arguments}) {
push(@t, $argument->type);
if ($argument->isOptional) {
push(@o, "optional");
} elsif ($argument->isVariadic) {
push(@o, "variadic");
$isVariadic = 1;
} else {
push(@o, "required");
}
}
&$addTuple([$overload, [@t], [@o]]);
if ($isVariadic) {
my @newT = @t;
my @newO = @o;
for (my $i = $n; $i < $m; $i++) {
push(@newT, $t[-1]);
push(@newO, "variadic");
&$addTuple([$overload, [@newT], [@newO]]);
}
}
for (my $i = $n - 1; $i >= 0; $i--) {
my $argument = @{$overload->arguments}[$i];
last unless ($argument->isOptional || $argument->isVariadic);
pop(@t);
pop(@o);
&$addTuple([$overload, [@t], [@o]]);
}
}
return %allSets;
}
sub IsIDLTypeDistinguishableWithUnionForOverloadResolution
{
my ($type, $unionSubtypes) = @_;
assert("First type should not be a union") if $type->isUnion;
for my $unionSubType (@$unionSubtypes) {
return 0 unless AreTypesDistinguishableForOverloadResolution($type, $unionSubType);
}
return 1;
}
# Determines if two types are distinguishable in the context of overload resolution,
# according to the Web IDL specification:
# http://heycam.github.io/webidl/#dfn-distinguishable
sub AreTypesDistinguishableForOverloadResolution
{
my ($typeA, $typeB) = @_;
my $isCallbackFunctionOrDictionary = sub {
my $type = shift;
return $codeGenerator->IsCallbackFunction($type) || $codeGenerator->IsDictionaryType($type);
};
# Two types are distinguishable for overload resolution if at most one of the two includes a nullable type.
return 0 if $typeA->isNullable && $typeB->isNullable;
# Union types: typeA and typeB are distinguishable if:
# - Both types are either a union type or nullable union type, and each member type of the one is
# distinguishable with each member type of the other.
# - One type is a union type or nullable union type, the other is neither a union type nor a nullable
# union type, and each member type of the first is distinguishable with the second.
if ($typeA->isUnion && $typeB->isUnion) {
for my $unionASubType (@{$typeA->subtypes}) {
return 0 unless IsIDLTypeDistinguishableWithUnionForOverloadResolution($unionASubType, $typeB->subtypes);
}
return 1;
} elsif ($typeA->isUnion) {
return IsIDLTypeDistinguishableWithUnionForOverloadResolution($typeB, $typeA->subtypes);
} elsif ($typeB->isUnion) {
return IsIDLTypeDistinguishableWithUnionForOverloadResolution($typeA, $typeB->subtypes);
}
return 0 if $typeA->name eq $typeB->name;
return 0 if $typeA->name eq "object" or $typeB->name eq "object";
return 0 if $codeGenerator->IsNumericType($typeA) && $codeGenerator->IsNumericType($typeB);
return 0 if $codeGenerator->IsStringOrEnumType($typeA) && $codeGenerator->IsStringOrEnumType($typeB);
return 0 if $codeGenerator->IsDictionaryType($typeA) && $codeGenerator->IsDictionaryType($typeB);
return 0 if $codeGenerator->IsCallbackInterface($typeA) && $codeGenerator->IsCallbackInterface($typeB);
return 0 if &$isCallbackFunctionOrDictionary($typeA) && &$isCallbackFunctionOrDictionary($typeB);
return 0 if $codeGenerator->IsSequenceOrFrozenArrayType($typeA) && $codeGenerator->IsSequenceOrFrozenArrayType($typeB);
# FIXME: return 0 if $typeA and $typeB are both exception types.
return 1;
}
# If there is more than one entry in an effective overload set that has a given type list length,
# then for those entries there must be an index i such that for each pair of entries the types
# at index i are distinguishable. The lowest such index is termed the distinguishing argument index.
# http://heycam.github.io/webidl/#dfn-distinguishing-argument-index
sub GetDistinguishingArgumentIndex
{
my ($operation, $S) = @_;
# FIXME: Consider all the tuples, not just the 2 first ones?
my $firstTupleTypes = @{@{$S}[0]}[1];
my $secondTupleTypes = @{@{$S}[1]}[1];
for (my $index = 0; $index < scalar(@$firstTupleTypes); $index++) {
return $index if AreTypesDistinguishableForOverloadResolution(@{$firstTupleTypes}[$index], @{$secondTupleTypes}[$index]);
}
die "Undistinguishable overloads for operation " . $operation->name . " with length: " . scalar(@$firstTupleTypes);
}
sub GetOverloadThatMatches
{
my ($S, $parameterIndex, $matches) = @_;
for my $tuple (@{$S}) {
my $type = @{@{$tuple}[1]}[$parameterIndex];
my $optionality = @{@{$tuple}[2]}[$parameterIndex];
if ($type->isUnion) {
for my $subtype (GetFlattenedMemberTypes($type)) {
return @{$tuple}[0] if $matches->($subtype, $optionality);
}
} else {
return @{$tuple}[0] if $matches->($type, $optionality);
}
}
}
sub GetOverloadThatMatchesIgnoringUnionSubtypes
{
my ($S, $parameterIndex, $matches) = @_;
for my $tuple (@{$S}) {
my $type = @{@{$tuple}[1]}[$parameterIndex];
my $optionality = @{@{$tuple}[2]}[$parameterIndex];
return @{$tuple}[0] if $matches->($type, $optionality);
}
}
sub GetConditionalForOperationConsideringOverloads
{
my $operation = shift;
return $operation->extendedAttributes->{Conditional} unless $operation->{overloads};
my %conditions;
foreach my $overload (@{$operation->{overloads}}) {
my $conditional = $overload->extendedAttributes->{Conditional};
return unless $conditional;
$conditions{$conditional} = 1;
}
return join("|", keys %conditions);
}
# Implements the overload resolution algorithm, as defined in the Web IDL specification:
# http://heycam.github.io/webidl/#es-overloads
sub GenerateOverloadDispatcher
{
my ($operation, $interface, $overloadFunctionPrefix, $overloadFunctionSuffix, $parametersToForward) = @_;
my %allSets = ComputeEffectiveOverloadSet($operation->{overloads});
my $generateOverloadCallIfNecessary = sub {
my ($overload, $condition, $conditionCanThrow, $include) = @_;
return unless $overload;
my $conditionalString = $codeGenerator->GenerateConditionalString($overload);
push(@implContent, "#if ${conditionalString}\n") if $conditionalString;
if ($condition && $conditionCanThrow) {
push(@implContent, " {\n");
push(@implContent, " bool success = $condition;\n");
push(@implContent, " RETURN_IF_EXCEPTION(throwScope, { });\n");
push(@implContent, " if (success)\n");
push(@implContent, " RELEASE_AND_RETURN(throwScope, (" . $overloadFunctionPrefix . $overload->{overloadIndex} . $overloadFunctionSuffix . "(${parametersToForward})));\n");
push(@implContent, " }\n");
} elsif ($condition) {
push(@implContent, " if ($condition)\n");
push(@implContent, " RELEASE_AND_RETURN(throwScope, (" . $overloadFunctionPrefix . $overload->{overloadIndex} . $overloadFunctionSuffix . "(${parametersToForward})));\n");
} else {
push(@implContent, " RELEASE_AND_RETURN(throwScope, (" . $overloadFunctionPrefix . $overload->{overloadIndex} . $overloadFunctionSuffix . "(${parametersToForward})));\n");
}
push(@implContent, "#endif\n") if $conditionalString;
AddToImplIncludes($include, $overload->extendedAttributes->{Conditional}) if $include;
};
my $isOptionalParameter = sub {
my ($type, $optionality) = @_;
return $optionality eq "optional";
};
my $isDictionaryOrRecordParameter = sub {
my ($type, $optionality) = @_;
return $codeGenerator->IsDictionaryType($type) || $codeGenerator->IsRecordType($type);
};
my $isNullableOrDictionaryOrRecordOrUnionContainingOne = sub {
my ($type, $optionality) = @_;
return 1 if $type->isNullable;
if ($type->isUnion) {
for my $subtype (GetFlattenedMemberTypes($type)) {
return 1 if $type->isNullable || &$isDictionaryOrRecordParameter($subtype, $optionality);
}
return 0;
} else {
return &$isDictionaryOrRecordParameter($type, $optionality);
}
};
my $isObjectOrErrorParameter = sub {
my ($type, $optionality) = @_;
return $type->name eq "object" || $type->name eq "Error";
};
my $isObjectOrErrorOrDOMExceptionParameter = sub {
my ($type, $optionality) = @_;
return 1 if &$isObjectOrErrorParameter($type, $optionality);
return $type->name eq "DOMException";
};
my $isObjectOrCallbackFunctionParameter = sub {
my ($type, $optionality) = @_;
return $type->name eq "object" || $codeGenerator->IsCallbackFunction($type);
};
my $isSequenceOrFrozenArrayParameter = sub {
my ($type, $optionality) = @_;
return $codeGenerator->IsSequenceOrFrozenArrayType($type);
};
my $isDictionaryOrRecordOrObjectOrCallbackInterfaceParameter = sub {
my ($type, $optionality) = @_;
return 1 if &$isDictionaryOrRecordParameter($type, $optionality);
return 1 if $type->name eq "object";
return 1 if $codeGenerator->IsCallbackInterface($type) && !$codeGenerator->IsCallbackFunction($type);
return 0;
};
my $isBooleanParameter = sub {
my ($type, $optionality) = @_;
return $type->name eq "boolean";
};
my $isNumericParameter = sub {
my ($type, $optionality) = @_;
return $codeGenerator->IsNumericType($type);
};
my $isStringOrEnumParameter = sub {
my ($type, $optionality) = @_;
return $codeGenerator->IsStringOrEnumType($type);
};
my $isAnyParameter = sub {
my ($type, $optionality) = @_;
return $type->name eq "any";
};
my $maxArgCount = LengthOfLongestOperationParameterList($operation->{overloads});
push(@implContent, " size_t argsCount = std::min<size_t>(${maxArgCount}, callFrame->argumentCount());\n");
for my $length ( sort keys %allSets ) {
push(@implContent, " if (argsCount == ${length}) {\n");
my $S = $allSets{$length};
if (scalar(@$S) > 1) {
my $d = GetDistinguishingArgumentIndex($operation, $S);
push(@implContent, " JSValue distinguishingArg = callFrame->uncheckedArgument($d);\n");
my $overload = GetOverloadThatMatchesIgnoringUnionSubtypes($S, $d, \&$isOptionalParameter);
&$generateOverloadCallIfNecessary($overload, "distinguishingArg.isUndefined()");
$overload = GetOverloadThatMatchesIgnoringUnionSubtypes($S, $d, \&$isNullableOrDictionaryOrRecordOrUnionContainingOne);
&$generateOverloadCallIfNecessary($overload, "distinguishingArg.isUndefinedOrNull()");
for my $tuple (@{$S}) {
my $overload = @{$tuple}[0];
my $type = @{@{$tuple}[1]}[$d];
my @subtypes = $type->isUnion ? GetFlattenedMemberTypes($type) : ( $type );
for my $subtype (@subtypes) {
if ($codeGenerator->IsWrapperType($subtype) || $codeGenerator->IsBufferSourceType($subtype)) {
if ($subtype->name eq "DOMWindow") {
AddToImplIncludes("JSWindowProxy.h");
&$generateOverloadCallIfNecessary($overload, "distinguishingArg.isObject() && (asObject(distinguishingArg)->inherits<JSWindowProxy>(vm) || asObject(distinguishingArg)->inherits<JSDOMWindow>(vm))");
} elsif ($subtype->name eq "RemoteDOMWindow") {
AddToImplIncludes("JSWindowProxy.h");
&$generateOverloadCallIfNecessary($overload, "distinguishingArg.isObject() && (asObject(distinguishingArg)->inherits<JSWindowProxy>(vm) || asObject(distinguishingArg)->inherits<JSRemoteDOMWindow>(vm))");
} else {
&$generateOverloadCallIfNecessary($overload, "distinguishingArg.isObject() && asObject(distinguishingArg)->inherits<JS" . $subtype->name . ">(vm)");
}
}
}
}
$overload = GetOverloadThatMatches($S, $d, \&$isObjectOrErrorOrDOMExceptionParameter);
&$generateOverloadCallIfNecessary($overload, "distinguishingArg.isObject() && asObject(distinguishingArg)->inherits<JSDOMException>(vm)");
$overload = GetOverloadThatMatches($S, $d, \&$isObjectOrErrorParameter);
&$generateOverloadCallIfNecessary($overload, "distinguishingArg.isObject() && asObject(distinguishingArg)->type() == ErrorInstanceType");
$overload = GetOverloadThatMatches($S, $d, \&$isObjectOrCallbackFunctionParameter);
&$generateOverloadCallIfNecessary($overload, "distinguishingArg.isCallable(vm)");
# FIXME: Avoid invoking GetMethod(object, Symbol.iterator) again in convert<IDLSequence<T>>(...).
$overload = GetOverloadThatMatches($S, $d, \&$isSequenceOrFrozenArrayParameter);
&$generateOverloadCallIfNecessary($overload, "hasIteratorMethod(lexicalGlobalObject, distinguishingArg)", 1, "<JavaScriptCore/IteratorOperations.h>");
$overload = GetOverloadThatMatches($S, $d, \&$isDictionaryOrRecordOrObjectOrCallbackInterfaceParameter);
&$generateOverloadCallIfNecessary($overload, "distinguishingArg.isObject()");
my $booleanOverload = GetOverloadThatMatches($S, $d, \&$isBooleanParameter);
&$generateOverloadCallIfNecessary($booleanOverload, "distinguishingArg.isBoolean()");
my $numericOverload = GetOverloadThatMatches($S, $d, \&$isNumericParameter);
&$generateOverloadCallIfNecessary($numericOverload, "distinguishingArg.isNumber()");
# Fallbacks.
$overload = GetOverloadThatMatches($S, $d, \&$isStringOrEnumParameter);
if ($overload) {
&$generateOverloadCallIfNecessary($overload);
} elsif ($numericOverload) {
&$generateOverloadCallIfNecessary($numericOverload);
} elsif ($booleanOverload) {
&$generateOverloadCallIfNecessary($booleanOverload);
} else {
$overload = GetOverloadThatMatches($S, $d, \&$isAnyParameter);
&$generateOverloadCallIfNecessary($overload);
}
} else {
# Only 1 overload with this number of parameters.
my $overload = @{@{$S}[0]}[0];
&$generateOverloadCallIfNecessary($overload);
}
push(@implContent, <<END);
}
END
}
my $minArgCount = GetFunctionLength($operation);
if ($minArgCount > 0) {
push(@implContent, " return argsCount < $minArgCount ? throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)) : throwVMTypeError(lexicalGlobalObject, throwScope);\n")
} else {
push(@implContent, " return throwVMTypeError(lexicalGlobalObject, throwScope);\n")
}
}
# As per Web IDL specification, the length of a function Object is its number of mandatory parameters.
sub GetFunctionLength
{
my $operation = shift;
my $getOverloadLength = sub {
my $operation = shift;
my $length = 0;
foreach my $argument (@{$operation->arguments}) {
last if $argument->isOptional || $argument->isVariadic;
$length++;
}
return $length;
};
my $length = &$getOverloadLength($operation);
foreach my $overload (@{$operation->{overloads}}) {
my $newLength = &$getOverloadLength($overload);
$length = $newLength if $newLength < $length;
}
return $length;
}
sub LengthOfLongestOperationParameterList
{
my ($overloads) = @_;
my $result = 0;
foreach my $overload (@{$overloads}) {
my @arguments = @{$overload->arguments};
$result = @arguments if $result < @arguments;
}
return $result;
}
# See http://refspecs.linux-foundation.org/cxxabi-1.83.html.
sub GetGnuVTableRefForInterface
{
my $interface = shift;
my $vtableName = GetGnuVTableNameForInterface($interface);
if (!$vtableName) {
return "0";
}
my $typename = $interface->type->name;
my $offset = GetGnuVTableOffsetForType($typename);
return "&" . $vtableName . "[" . $offset . "]";
}
sub GetGnuVTableNameForInterface
{
my $interface = shift;
my $typename = $interface->type->name;
my $templatePosition = index($typename, "<");
return "" if $templatePosition != -1;
return "" if GetImplementationLacksVTableForInterface($interface);
return "" if GetSkipVTableValidationForInterface($interface);
return "_ZTV" . GetGnuMangledNameForInterface($interface);
}
sub GetGnuMangledNameForInterface
{
my $interface = shift;
my $typename = $interface->type->name;
my $templatePosition = index($typename, "<");
if ($templatePosition != -1) {
return "";
}
my $mangledType = length($typename) . $typename;
my $namespace = "WebCore";
my $mangledNamespace = "N" . length($namespace) . $namespace;
return $mangledNamespace . $mangledType . "E";
}
sub GetGnuVTableOffsetForType
{
my $typename = shift;
if ($typename eq "ApplePaySession"
|| $typename eq "SVGAElement"
|| $typename eq "SVGCircleElement"
|| $typename eq "SVGClipPathElement"
|| $typename eq "SVGDefsElement"
|| $typename eq "SVGEllipseElement"
|| $typename eq "SVGForeignObjectElement"
|| $typename eq "SVGGElement"
|| $typename eq "SVGImageElement"
|| $typename eq "SVGLineElement"
|| $typename eq "SVGPathElement"
|| $typename eq "SVGPolyElement"
|| $typename eq "SVGPolygonElement"
|| $typename eq "SVGPolylineElement"
|| $typename eq "SVGRectElement"
|| $typename eq "SVGSVGElement"
|| $typename eq "SVGGeometryElement"
|| $typename eq "SVGGraphicsElement"
|| $typename eq "SVGSwitchElement"
|| $typename eq "SVGTextElement"
|| $typename eq "SVGUseElement") {
return "3";
}
return "2";
}
# See http://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B_Name_Mangling.
sub GetWinVTableRefForInterface
{
my $interface = shift;
my $vtableName = GetWinVTableNameForInterface($interface);
return 0 if !$vtableName;
return "__identifier(\"" . $vtableName . "\")";
}
sub GetWinVTableNameForInterface
{
my $interface = shift;
my $typename = $interface->type->name;
my $templatePosition = index($typename, "<");
return "" if $templatePosition != -1;
return "" if GetImplementationLacksVTableForInterface($interface);
return "" if GetSkipVTableValidationForInterface($interface);
return "??_7" . GetWinMangledNameForInterface($interface) . "6B@";
}
sub GetWinMangledNameForInterface
{
my $interface = shift;
my $typename = $interface->type->name;
my $namespace = "WebCore";
return $typename . "@" . $namespace . "@@";
}
sub GetImplementationLacksVTableForInterface
{
my $interface = shift;
return $interface->extendedAttributes->{ImplementationLacksVTable};
}
sub GetSkipVTableValidationForInterface
{
my $interface = shift;
return $interface->extendedAttributes->{SkipVTableValidation};
}
# URL becomes url, but SetURL becomes setURL.
sub ToMethodName
{
my $param = shift;
return $param if $param =~ /^CSSOM/;
my $ret = lcfirst($param);
$ret =~ s/cSS/css/ if $ret =~ /^cSS/;
$ret =~ s/dOM/dom/ if $ret =~ /^dOM/;
$ret =~ s/hTML/html/ if $ret =~ /^hTML/;
$ret =~ s/hDR/hdr/ if $ret =~ /^hDR/;
$ret =~ s/jS/js/ if $ret =~ /^jS/;
$ret =~ s/uRL/url/ if $ret =~ /^uRL/;
$ret =~ s/xML/xml/ if $ret =~ /^xML/;
$ret =~ s/xSLT/xslt/ if $ret =~ /^xSLT/;
# 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 GenerateRuntimeEnableConditionalStringForExposed
{
my ($interface, $context, $globalObjectPtr, $conjuncts) = @_;
assert("Must specify value for Exposed.") if $context->extendedAttributes->{Exposed} eq "VALUE_IS_MISSING";
AddToImplIncludes("ScriptExecutionContext.h");
my $exposed = $context->extendedAttributes->{Exposed};
if (ref($exposed) eq 'ARRAY') {
if (scalar(@$exposed) > 1) {
return;
}
$exposed = @$exposed[0];
}
if ($exposed eq "Window") {
push(@$conjuncts, "jsCast<JSDOMGlobalObject*>(" . $globalObjectPtr . ")->scriptExecutionContext()->isDocument()");
} elsif ($exposed eq "Worker") {
push(@$conjuncts, "jsCast<JSDOMGlobalObject*>(" . $globalObjectPtr . ")->scriptExecutionContext()->isWorkerGlobalScope()");
} elsif ($exposed eq "Worklet") {
push(@$conjuncts, "jsCast<JSDOMGlobalObject*>(" . $globalObjectPtr . ")->scriptExecutionContext()->isWorkletGlobalScope()");
} elsif ($exposed eq "AudioWorklet") {
push(@$conjuncts, "is<AudioWorkletGlobalScope>(jsCast<JSDOMGlobalObject*>(" . $globalObjectPtr . ")->scriptExecutionContext())");
} else {
assert("Unrecognized value '" . Dumper($context->extendedAttributes->{Exposed}) . "' for the Exposed extended attribute on '" . ref($context) . "'.");
}
}
# Returns the conditional string that determines whether a method/attribute is enabled at runtime.
# A method/attribute is enabled at runtime if either its RuntimeEnabledFeatures function returns
# true or its EnabledForWorld function returns true (or both).
# NOTE: Parameter passed in must have an 'extendedAttributes' property.
# (e.g. IDLInterface, IDLAttribute, IDLOperation, IDLIterable, etc.)
sub GenerateRuntimeEnableConditionalString
{
my ($interface, $context, $globalObjectPtr) = @_;
my @conjuncts;
if ($context->extendedAttributes->{SecureContext}) {
AddToImplIncludes("ScriptExecutionContext.h");
if ($context->extendedAttributes->{ContextAllowsMediaDevices}) {
push(@conjuncts, "(jsCast<JSDOMGlobalObject*>(" . $globalObjectPtr . ")->scriptExecutionContext()->isSecureContext()"
. "|| jsCast<JSDOMGlobalObject*>(" . $globalObjectPtr . ")->scriptExecutionContext()->allowsMediaDevices())");
} else {
push(@conjuncts, "jsCast<JSDOMGlobalObject*>(globalObject())->scriptExecutionContext()->isSecureContext()");
}
}
if ($context->extendedAttributes->{Exposed}) {
GenerateRuntimeEnableConditionalStringForExposed($interface, $context, $globalObjectPtr, \@conjuncts);
}
if ($context->extendedAttributes->{EnabledForWorld}) {
assert("Must specify value for EnabledForWorld.") if $context->extendedAttributes->{EnabledForWorld} eq "VALUE_IS_MISSING";
AddToImplIncludes("DOMWrapperWorld.h");
push(@conjuncts, "worldForDOMObject(*this)." . ToMethodName($context->extendedAttributes->{EnabledForWorld}) . "()");
}
if ($context->extendedAttributes->{EnabledBySetting}) {
assert("Must specify value for EnabledBySetting.") if $context->extendedAttributes->{EnabledBySetting} eq "VALUE_IS_MISSING";
AddToImplIncludes("ScriptExecutionContext.h");
my @flags = split(/&/, $context->extendedAttributes->{EnabledBySetting});
foreach my $flag (@flags) {
push(@conjuncts, "jsCast<JSDOMGlobalObject*>(" . $globalObjectPtr . ")->scriptExecutionContext()->settingsValues()." . ToMethodName($flag) . "Enabled");
}
}
if ($context->extendedAttributes->{SettingsConditionallyReadWrite}) {
assert("Must specify value for SettingsConditionallyReadWrite.") if $context->extendedAttributes->{SettingsConditionallyReadWrite} eq "VALUE_IS_MISSING";
AddToImplIncludes("Document.h");
assert("SettingsConditionallyReadWrite can only be used by interfaces only exposed to the Window") if $interface->extendedAttributes->{Exposed} && $interface->extendedAttributes->{Exposed} ne "Window";
my @flags = split(/&/, $context->extendedAttributes->{SettingsConditionallyReadWrite});
foreach my $flag (@flags) {
push(@conjuncts, "downcast<Document>(jsCast<JSDOMGlobalObject*>(" . $globalObjectPtr . ")->scriptExecutionContext())->settingsValues()." . ToMethodName($flag) . "Enabled");
}
}
if ($context->extendedAttributes->{CustomEnabled}) {
assert("CustomEnabled can only be used by interfaces only exposed to the Window") if $interface->extendedAttributes->{Exposed} && $interface->extendedAttributes->{Exposed} ne "Window";
my $className = "JS" . $interface->type->name;
push(@conjuncts, "${className}" . $codeGenerator->WK_ucfirst($context->name) . "IsEnabled()");
}
if ($context->extendedAttributes->{EnabledByQuirk}) {
assert("Must specify value for EnabledByQuirk.") if $context->extendedAttributes->{DisabledByQuirk} eq "VALUE_IS_MISSING";
AddToImplIncludes("Document.h");
AddToImplIncludes("Quirks.h");
assert("EnabledByQuirk can only be used by interfaces only exposed to the Window") if $interface->extendedAttributes->{Exposed} && $interface->extendedAttributes->{Exposed} ne "Window";
my @flags = split(/&/, $context->extendedAttributes->{EnabledByQuirk});
foreach my $flag (@flags) {
push(@conjuncts, "downcast<Document>(jsCast<JSDOMGlobalObject*>(" . $globalObjectPtr . ")->scriptExecutionContext())->quirks()." . ToMethodName($flag) . "Quirk()");
}
}
if ($context->extendedAttributes->{DisabledByQuirk}) {
assert("Must specify value for DisabledByQuirk.") if $context->extendedAttributes->{DisabledByQuirk} eq "VALUE_IS_MISSING";
AddToImplIncludes("Document.h");
AddToImplIncludes("Quirks.h");
assert("DisabledByQuirk can only be used by interfaces only exposed to the Window") if $interface->extendedAttributes->{Exposed} && $interface->extendedAttributes->{Exposed} ne "Window";
my @flags = split(/&/, $context->extendedAttributes->{DisabledByQuirk});
foreach my $flag (@flags) {
push(@conjuncts, "!downcast<Document>(jsCast<JSDOMGlobalObject*>(" . $globalObjectPtr . ")->scriptExecutionContext())->quirks()." . ToMethodName($flag) . "Quirk()");
}
}
if ($context->extendedAttributes->{EnabledAtRuntime}) {
assert("Must specify value for EnabledAtRuntime.") if $context->extendedAttributes->{EnabledAtRuntime} eq "VALUE_IS_MISSING";
AddToImplIncludes("RuntimeEnabledFeatures.h");
my @flags = split(/&/, $context->extendedAttributes->{EnabledAtRuntime});
foreach my $flag (@flags) {
push(@conjuncts, "RuntimeEnabledFeatures::sharedFeatures()." . ToMethodName($flag) . "Enabled()");
}
}
if ($context->extendedAttributes->{RuntimeConditionallyReadWrite}) {
assert("Must specify value for RuntimeConditionallyReadWrite.") if $context->extendedAttributes->{RuntimeConditionallyReadWrite} eq "VALUE_IS_MISSING";
AddToImplIncludes("RuntimeEnabledFeatures.h");
my @flags = split(/&/, $context->extendedAttributes->{RuntimeConditionallyReadWrite});
foreach my $flag (@flags) {
push(@conjuncts, "RuntimeEnabledFeatures::sharedFeatures()." . ToMethodName($flag) . "Enabled()");
}
}
if ($context->extendedAttributes->{EnabledForContext}) {
assert("Must not specify value for EnabledForContext.") unless $context->extendedAttributes->{EnabledForContext} eq "VALUE_IS_MISSING";
assert("EnabledForContext must be an interface or constructor attribute.") unless $codeGenerator->IsConstructorType($context->type);
my $contextRef = "*jsCast<JSDOMGlobalObject*>(" . $globalObjectPtr . ")->scriptExecutionContext()";
my $name = $context->name;
push(@conjuncts, "${name}::enabledForContext(" . $contextRef . ")");
}
if ($context->extendedAttributes->{LegacyFactoryFunctionEnabledBySetting}) {
assert("Must specify value for LegacyFactoryFunctionEnabledBySetting.") if $context->extendedAttributes->{LegacyFactoryFunctionEnabledBySetting} eq "VALUE_IS_MISSING";
my @settings = split(/&/, $context->extendedAttributes->{LegacyFactoryFunctionEnabledBySetting});
foreach my $setting (@settings) {
push(@conjuncts, "downcast<Document>(jsCast<JSDOMGlobalObject*>(" . $globalObjectPtr . ")->scriptExecutionContext())->settings()." . ToMethodName($setting) . "Enabled()");
}
}
my $result = join(" && ", @conjuncts);
$result = "($result)" if @conjuncts > 1;
return $result;
}
sub GetCastingHelperForThisObject
{
my $interface = shift;
my $interfaceName = $interface->type->name;
return "jsDynamicCast<JS$interfaceName*>";
}
# http://heycam.github.io/webidl/#Unscopable
sub addUnscopableProperties
{
my $interface = shift;
my @unscopables;
foreach my $operationOrAttribute (@{$interface->operations}, @{$interface->attributes}) {
push(@unscopables, $operationOrAttribute->name) if $operationOrAttribute->extendedAttributes->{Unscopable};
}
return if scalar(@unscopables) == 0;
AddToImplIncludes("<JavaScriptCore/ObjectConstructor.h>");
push(@implContent, " JSObject& unscopables = *constructEmptyObject(globalObject()->vm(), globalObject()->nullPrototypeObjectStructure());\n");
foreach my $unscopable (@unscopables) {
push(@implContent, " unscopables.putDirect(vm, Identifier::fromString(vm, \"$unscopable\"), jsBoolean(true));\n");
}
push(@implContent, " putDirectWithoutTransition(vm, vm.propertyNames->unscopablesSymbol, &unscopables, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly);\n");
}
sub GetArgumentTypeForFunctionWithoutTypeCheck
{
my ($interface, $type) = @_;
my $IDLType = GetIDLType($interface, $type);
return "DOMJIT::IDLJSArgumentType<${IDLType}>";
}
sub GetArgumentTypeFilter
{
my ($interface, $type) = @_;
my $IDLType = GetIDLType($interface, $type);
return "DOMJIT::IDLArgumentTypeFilter<${IDLType}>::value";
}
sub GetResultTypeFilter
{
my ($interface, $type) = @_;
my $IDLType = GetIDLType($interface, $type);
return "DOMJIT::IDLResultTypeFilter<${IDLType}>::value";
}
sub GetAttributeWithName
{
my ($interface, $attributeName) = @_;
foreach my $attribute (@{$interface->attributes}) {
return $attribute if $attribute->name eq $attributeName;
}
}
# https://heycam.github.io/webidl/#es-iterator
sub InterfaceNeedsIterator
{
my ($interface) = @_;
return 1 if $interface->setLike;
return 1 if $interface->mapLike;
return 1 if $interface->iterable;
if (GetIndexedGetterOperation($interface)) {
my $lengthAttribute = GetAttributeWithName($interface, "length");
return 1 if $lengthAttribute and $codeGenerator->IsIntegerType($lengthAttribute->type);
}
return 0;
}
sub GenerateImplementation
{
my ($object, $interface, $enumerations, $dictionaries) = @_;
my $interfaceName = $interface->type->name;
my $className = "JS$interfaceName";
my $hasParent = $interface->parentType || $interface->extendedAttributes->{JSLegacyParent};
my $parentClassName = GetParentClassName($interface);
my $visibleInterfaceName = $codeGenerator->GetVisibleInterfaceName($interface);
my $needsVisitChildren = InstanceNeedsVisitChildren($interface);
my $namedGetterOperation = GetNamedGetterOperation($interface);
my $indexedGetterOperation = GetIndexedGetterOperation($interface);
# - Add default header template
push(@implContentHeader, GenerateImplementationContentHeader($interface));
AddToImplIncludes("<JavaScriptCore/JSCInlines.h>");
AddToImplIncludes("JSDOMBinding.h");
AddToImplIncludes("JSDOMExceptionHandling.h");
AddToImplIncludes("JSDOMWrapperCache.h");
AddToImplIncludes("<wtf/GetPtr.h>");
AddToImplIncludes("<wtf/PointerPreparations.h>");
AddToImplIncludes("<JavaScriptCore/PropertyNameArray.h>") if $indexedGetterOperation;
AddToImplIncludes("JSDOMMapLike.h") if $interface->mapLike;
AddToImplIncludes("JSDOMSetLike.h") if $interface->setLike;
AddJSBuiltinIncludesIfNeeded($interface);
my $implType = GetImplClassName($interface);
AddToImplIncludes("${implType}.h") if $interface->isNamespaceObject;
@implContent = ();
push(@implContent, "\n\nnamespace WebCore {\n");
push(@implContent, "using namespace JSC;\n\n");
push(@implContent, GenerateEnumerationsImplementationContent($interface, $enumerations));
push(@implContent, GenerateDictionariesImplementationContent($interface, $dictionaries));
my $numConstants = @{$interface->constants};
my $numAttributes = @{$interface->attributes};
my $numOperations = @{$interface->operations};
if ($numOperations > 0) {
my $inAppleCopyright = 0;
push(@implContent,"// Functions\n\n");
foreach my $operation (@{$interface->operations}) {
next if $operation->{overloadIndex} && $operation->{overloadIndex} > 1;
next if IsJSBuiltin($interface, $operation);
next if $operation->name eq "[Symbol.Iterator]";
if ($operation->extendedAttributes->{AppleCopyright}) {
if (!$inAppleCopyright) {
push(@implContent, $beginAppleCopyrightForHeaderFiles);
$inAppleCopyright = 1;
}
} elsif ($inAppleCopyright) {
push(@implContent, $endAppleCopyright);
$inAppleCopyright = 0;
}
my $conditionalAttribute = GetConditionalForOperationConsideringOverloads($operation);
my $conditionalString = $conditionalAttribute ? $codeGenerator->GenerateConditionalStringFromAttributeValue($conditionalAttribute) : undef;
push(@implContent, "#if ${conditionalString}\n") if $conditionalString;
my $functionName = GetFunctionName($interface, $className, $operation);
push(@implContent, "static JSC_DECLARE_HOST_FUNCTION(${functionName});\n");
if ($operation->extendedAttributes->{DOMJIT}) {
$implIncludes{"DOMJITIDLType.h"} = 1;
my $nameOfFunctionWithoutTypeCheck = $codeGenerator->WK_lcfirst($functionName) . "WithoutTypeCheck";
my $functionSignature = "static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(${nameOfFunctionWithoutTypeCheck}, JSC::EncodedJSValue, (JSC::JSGlobalObject*, $className*";
foreach my $argument (@{$operation->arguments}) {
my $type = $argument->type;
my $argumentType = GetArgumentTypeForFunctionWithoutTypeCheck($interface, $type);
$functionSignature .= ", ${argumentType}";
}
push(@implContent, $functionSignature . "));\n");
}
push(@implContent, "#endif\n") if $conditionalString;
}
push(@implContent, $endAppleCopyright) if $inAppleCopyright;
push(@implContent, "\n");
}
if ($numAttributes > 0 || NeedsConstructorProperty($interface)) {
push(@implContent, "// Attributes\n\n");
if (NeedsConstructorProperty($interface)) {
my $constructorGetter = "js" . $interfaceName . "Constructor";
push(@implContent, "static JSC_DECLARE_CUSTOM_GETTER(${constructorGetter});\n");
}
foreach my $attribute (@{$interface->extendedAttributes->{SyntheticIDLAttributes}}, @{$interface->attributes}) {
GenerateAttributeGetterAndSetterDeclaration(\@implContent, $interface, $className, $attribute);
}
push(@implContent, "\n");
}
if ($numOperations > 0) {
foreach my $operation (@{$interface->operations}) {
next unless $operation->extendedAttributes->{DOMJIT};
$implIncludes{"DOMJITIDLTypeFilter.h"} = 1;
$implIncludes{"DOMJITAbstractHeapRepository.h"} = 1;
my $isOverloaded = $operation->{overloads} && @{$operation->{overloads}} > 1;
die "Overloads is not supported in DOMJIT" if $isOverloaded;
die "Currently ReadDOM value is only allowed" unless $codeGenerator->ExtendedAttributeContains($operation->extendedAttributes->{DOMJIT}, "ReadDOM");
my $functionName = GetFunctionName($interface, $className, $operation);
my $nameOfFunctionWithoutTypeCheck = $codeGenerator->WK_lcfirst($functionName) . "WithoutTypeCheck";
my $domJITSignatureName = "DOMJITSignatureFor" . $interface->type->name . $codeGenerator->WK_ucfirst($operation->name);
my $classInfo = "JS" . $interface->type->name . "::info()";
my $resultType = GetResultTypeFilter($interface, $operation->type);
my $domJITSignatureHeader = "static const JSC::DOMJIT::Signature ${domJITSignatureName}(${nameOfFunctionWithoutTypeCheck},";
my $domJITSignatureFooter = "$classInfo, JSC::DOMJIT::Effect::forRead(DOMJIT::AbstractHeapRepository::DOM), ${resultType}";
foreach my $argument (@{$operation->arguments}) {
my $type = $argument->type;
my $argumentType = GetArgumentTypeFilter($interface, $type);
$domJITSignatureFooter .= ", ${argumentType}";
}
$domJITSignatureFooter .= ");";
my $conditionalString = $codeGenerator->GenerateConditionalString($operation);
push(@implContent, "#if ${conditionalString}\n") if $conditionalString;
push(@implContent, "$domJITSignatureHeader $domJITSignatureFooter\n");
push(@implContent, "#endif\n") if $conditionalString;
push(@implContent, "\n");
}
}
if ($numAttributes > 0 || NeedsConstructorProperty($interface)) {
foreach my $attribute (@{$interface->attributes}) {
next unless $attribute->extendedAttributes->{DOMJIT};
assert("Only DOMJIT=Getter is supported for attributes") unless $codeGenerator->ExtendedAttributeContains($attribute->extendedAttributes->{DOMJIT}, "Getter");
my $conditionalString = $codeGenerator->GenerateConditionalString($attribute);
push(@implContent, "#if ${conditionalString}\n\n") if $conditionalString;
AddToImplIncludes("DOMJITIDLTypeFilter.h", $conditionalString);
my $generatorName = $interfaceName . $codeGenerator->WK_ucfirst($attribute->name);
my $domJITClassName = $generatorName . "Attribute";
my $getter = GetAttributeGetterName($interface, $className, $attribute);
my $resultType = "JSC::SpecBytecodeTop";
if ($attribute->extendedAttributes->{DOMJIT}) {
$resultType = GetResultTypeFilter($interface, $attribute->type);
}
push(@implContent, "static const JSC::DOMJIT::GetterSetter DOMJITAttributeFor${generatorName} {\n");
push(@implContent, " $getter,\n");
push(@implContent, "#if ENABLE(JIT)\n");
push(@implContent, " &compile${domJITClassName},\n");
push(@implContent, "#else\n");
push(@implContent, " nullptr,\n");
push(@implContent, "#endif\n");
push(@implContent, " $resultType\n");
push(@implContent, "};\n\n");
push(@implContent, "#endif\n\n") if $conditionalString;
}
}
GeneratePrototypeDeclaration(\@implContent, $className, $interface) if !HeaderNeedsPrototypeDeclaration($interface);
GenerateConstructorDeclaration(\@implContent, $className, $interface) if !$interface->extendedAttributes->{LegacyNoInterfaceObject};
my @hashKeys = ();
my @hashValue1 = ();
my @hashValue2 = ();
my @hashSpecials = ();
my %conditionals = ();
my %readWriteConditionals = ();
my $hashName = $className . "Table";
my @runtimeEnabledOperations = ();
my @runtimeEnabledAttributes = ();
my @runtimeEnabledConstants = ();
# Generate hash table for properties on the instance.
my $numInstanceProperties = GeneratePropertiesHashTable($object, $interface, 1, \@hashKeys, \@hashSpecials, \@hashValue1, \@hashValue2, \%conditionals, \%readWriteConditionals, \@runtimeEnabledOperations, \@runtimeEnabledAttributes);
$object->GenerateHashTable($className, $hashName, $numInstanceProperties, \@hashKeys, \@hashSpecials, \@hashValue1, \@hashValue2, \%conditionals, \%readWriteConditionals, 0) if $numInstanceProperties > 0;
# - Add all interface object (aka constructor) properties (constants, static attributes, static operations).
if (!$interface->extendedAttributes->{LegacyNoInterfaceObject}) {
my $hashSize = 0;
my $hashName = $className . "ConstructorTable";
my @hashKeys = ();
my @hashValue1 = ();
my @hashValue2 = ();
my @hashSpecials = ();
my %conditionals = ();
my %readWriteConditionals = ();
foreach my $constant (@{$interface->constants}) {
my $name = $constant->name;
push(@hashKeys, $name);
push(@hashValue1, $constant->value);
push(@hashValue2, "0");
push(@hashSpecials, "JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger");
my $implementedBy = $constant->extendedAttributes->{ImplementedBy};
$implIncludes{"${implementedBy}.h"} = 1 if $implementedBy;
my $conditional = $constant->extendedAttributes->{Conditional};
$conditionals{$name} = $conditional if $conditional;
if (NeedsRuntimeCheck($interface, $constant)) {
push(@runtimeEnabledConstants, $constant);
}
$hashSize++;
}
foreach my $attribute (@{$interface->attributes}) {
next unless ($attribute->isStatic);
my $name = $attribute->name;
push(@hashKeys, $name);
my @specials = ();
push(@specials, "JSC::PropertyAttribute::DontDelete") if IsLegacyUnforgeable($interface, $attribute);
push(@specials, "JSC::PropertyAttribute::ReadOnly") if IsReadonly($attribute);
push(@specials, "JSC::PropertyAttribute::DOMAttribute") if IsAcceleratedDOMAttribute($interface, $attribute);
push(@specials, "JSC::PropertyAttribute::DOMJITAttribute") if $attribute->extendedAttributes->{DOMJIT};
my $special = "static_cast<unsigned>(" . ((@specials > 0) ? join(" | ", @specials) : "0") . ")";
push(@hashSpecials, $special);
if ($attribute->extendedAttributes->{DOMJIT}) {
push(@hashValue1, "&DOMJITAttributeFor" . $interface->type->name . $codeGenerator->WK_ucfirst($attribute->name));
} else {
my $getter = GetAttributeGetterName($interface, $className, $attribute);
push(@hashValue1, $getter);
}
if (IsReadonly($attribute)) {
push(@hashValue2, "0");
} else {
my $setter = GetAttributeSetterName($interface, $className, $attribute);
push(@hashValue2, $setter);
}
my $conditional = $attribute->extendedAttributes->{Conditional};
$conditionals{$name} = $conditional if $conditional;
my $readWriteConditional = $attribute->extendedAttributes->{ConditionallyReadWrite};
$readWriteConditionals{$name} = $readWriteConditional if $readWriteConditional;
$hashSize++;
}
foreach my $operation (@{$interface->operations}) {
next unless ($operation->isStatic);
next if $operation->{overloadIndex} && $operation->{overloadIndex} > 1;
my $name = $operation->name;
push(@hashKeys, $name);
my $functionName = GetFunctionName($interface, $className, $operation);
push(@hashValue1, $functionName);
my $functionLength = GetFunctionLength($operation);
if ($operation->extendedAttributes->{DOMJIT}) {
push(@hashValue2, "DOMJITFunctionFor" . $interface->type->name . $codeGenerator->WK_ucfirst($operation->name));
} else {
push(@hashValue2, $functionLength);
}
push(@hashSpecials, ComputeFunctionSpecial($interface, $operation));
my $conditional = $operation->extendedAttributes->{Conditional};
$conditionals{$name} = $conditional if $conditional;
$hashSize++;
}
$object->GenerateHashTable($className, $hashName, $hashSize, \@hashKeys, \@hashSpecials, \@hashValue1, \@hashValue2, \%conditionals, \%readWriteConditionals, 1) if $hashSize > 0;
push(@implContent, $codeGenerator->GenerateCompileTimeCheckForEnumsIfNeeded($interface));
my $protoClassName = "${className}Prototype";
GenerateConstructorDefinitions(\@implContent, $className, $protoClassName, $visibleInterfaceName, $interface);
my $legacyFactoryFunction = $interface->extendedAttributes->{LegacyFactoryFunction};
GenerateConstructorDefinitions(\@implContent, $className, $protoClassName, $legacyFactoryFunction, $interface, "GeneratingLegacyFactoryFunction") if $legacyFactoryFunction;
}
# - Add functions and constants to a hashtable definition
$hashName = $className . "PrototypeTable";
@hashKeys = ();
@hashValue1 = ();
@hashValue2 = ();
@hashSpecials = ();
%conditionals = ();
%readWriteConditionals = ();
@runtimeEnabledOperations = ();
@runtimeEnabledAttributes = ();
# Generate hash table for properties on the prototype.
my $numPrototypeProperties = GeneratePropertiesHashTable($object, $interface, 0,
\@hashKeys, \@hashSpecials,
\@hashValue1, \@hashValue2,
\%conditionals, \%readWriteConditionals,
\@runtimeEnabledOperations, \@runtimeEnabledAttributes);
my $hashSize = $numPrototypeProperties;
foreach my $constant (@{$interface->constants}) {
my $name = $constant->name;
push(@hashKeys, $name);
push(@hashValue1, $constant->value);
push(@hashValue2, "0");
push(@hashSpecials, "JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger");
my $conditional = $constant->extendedAttributes->{Conditional};
$conditionals{$name} = $conditional if $conditional;
$hashSize++;
}
if (!$interface->isNamespaceObject) {
my $justGenerateValueArray = !IsDOMGlobalObject($interface);
$object->GenerateHashTable($className, $hashName, $hashSize, \@hashKeys, \@hashSpecials, \@hashValue1, \@hashValue2, \%conditionals, \%readWriteConditionals, $justGenerateValueArray);
my $prototypeHashTable = $justGenerateValueArray ? "nullptr" : "&${className}PrototypeTable";
push(@implContent, "const ClassInfo ${className}Prototype::s_info = { \"${visibleInterfaceName}\", &Base::s_info, ${prototypeHashTable}, nullptr, CREATE_METHOD_TABLE(${className}Prototype) };\n\n");
push(@implContent, "void ${className}Prototype::finishCreation(VM& vm)\n");
push(@implContent, "{\n");
push(@implContent, " Base::finishCreation(vm);\n");
}
if (PrototypeHasStaticPropertyTable($interface) && !IsGlobalInterface($interface) && !$interface->isNamespaceObject) {
push(@implContent, " reifyStaticProperties(vm, ${className}::info(), ${className}PrototypeTableValues, *this);\n");
my @runtimeEnabledProperties = @runtimeEnabledOperations;
push(@runtimeEnabledProperties, @runtimeEnabledAttributes);
push(@runtimeEnabledProperties, @runtimeEnabledConstants);
if (@runtimeEnabledProperties) {
push(@implContent, " bool hasDisabledRuntimeProperties = false;\n");
}
foreach my $operationOrAttribute (@runtimeEnabledProperties) {
my $conditionalString = $codeGenerator->GenerateConditionalString($operationOrAttribute);
push(@implContent, "#if ${conditionalString}\n") if $conditionalString;
my $runtimeEnableConditionalString = GenerateRuntimeEnableConditionalString($interface, $operationOrAttribute, "globalObject()");
my $name = $operationOrAttribute->name;
push(@implContent, " if (!${runtimeEnableConditionalString}) {\n");
push(@implContent, " hasDisabledRuntimeProperties = true;\n");
push(@implContent, " auto propertyName = Identifier::fromString(vm, reinterpret_cast<const LChar*>(\"$name\"), strlen(\"$name\"));\n");
push(@implContent, " VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);\n");
push(@implContent, " DeletePropertySlot slot;\n");
push(@implContent, " JSObject::deleteProperty(this, globalObject(), propertyName, slot);\n");
push(@implContent, " }\n");
push(@implContent, "#endif\n") if $conditionalString;
}
foreach my $attribute (@runtimeEnabledAttributes) {
if (NeedsRuntimeReadWriteCheck($interface, $attribute)) {
AddToImplIncludes("WebCoreJSClientData.h");
my $runtimeEnableConditionalString = GenerateRuntimeEnableConditionalString($interface, $attribute, "globalObject()");
my $attributeName = $attribute->name;
my $getter = GetAttributeGetterName($interface, $className, $attribute);
my $setter = "nullptr";
my $jscAttributes = GetJSCAttributesForAttribute($interface, $attribute);
my $conditionalString = $codeGenerator->GenerateConditionalString($attribute);
push(@implContent, "#if ${conditionalString}\n") if $conditionalString;
push(@implContent, " // Adding back attribute, but as readonly, after removing the read-write variant above. \n");
push(@implContent, " if (!${runtimeEnableConditionalString})\n");
if (IsAcceleratedDOMAttribute($interface, $attribute)) {
my $classForThis = "${className}::info()";
push(@implContent, " putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames()." . $attributeName . "PublicName(), JSC::DOMAttributeGetterSetter::create(vm, $getter, $setter, JSC::DOMAttributeAnnotation { $classForThis, nullptr }), attributesForStructure($jscAttributes));\n");
} else {
assert("CustomGetterSetter is not allowed for DOMAttribute. DOMAttributeGetterSetter must be used.") if IsAcceleratedDOMAttribute($interface, $attribute);
push(@implContent, " putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames()." . $attributeName . "PublicName(), CustomGetterSetter::create(vm, $getter, $setter), attributesForStructure($jscAttributes));\n");
}
push(@implContent, "#endif\n") if $conditionalString;
}
}
if (@runtimeEnabledProperties) {
push(@implContent, " if (hasDisabledRuntimeProperties && structure()->isDictionary())\n");
push(@implContent, " flattenDictionaryObject(vm);\n");
}
foreach my $operation (@{$interface->operations}) {
next unless ($operation->extendedAttributes->{PrivateIdentifier});
AddToImplIncludes("WebCoreJSClientData.h");
my $conditionalString = $codeGenerator->GenerateConditionalString($operation);
push(@implContent, "#if ${conditionalString}\n") if $conditionalString;
push(@implContent, " putDirect(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames()." . $operation->name . "PrivateName(), JSFunction::create(vm, globalObject(), 0, String(), " . GetFunctionName($interface, $className, $operation) . "), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);\n");
push(@implContent, "#endif\n") if $conditionalString;
}
if (InterfaceNeedsIterator($interface)) {
AddToImplIncludes("<JavaScriptCore/BuiltinNames.h>");
if (IsKeyValueIterableInterface($interface) or $interface->mapLike or $interface->setLike) {
push(@implContent, " putDirect(vm, vm.propertyNames->iteratorSymbol, getDirect(vm, vm.propertyNames->builtinNames().entriesPublicName()), static_cast<unsigned>(JSC::PropertyAttribute::DontEnum));\n");
} else {
AddToImplIncludes("<JavaScriptCore/ArrayPrototype.h>");
push(@implContent, " putDirect(vm, vm.propertyNames->iteratorSymbol, globalObject()->arrayPrototype()->getDirect(vm, vm.propertyNames->builtinNames().valuesPrivateName()), static_cast<unsigned>(JSC::PropertyAttribute::DontEnum));\n");
}
}
push(@implContent, " addValueIterableMethods(*globalObject(), *this);\n") if $interface->iterable and !IsKeyValueIterableInterface($interface);
addUnscopableProperties($interface);
}
assert("JSC_TO_STRING_TAG_WITHOUT_TRANSITION() requires strings two or more characters long") if length($visibleInterfaceName) < 2;
if (!$interface->isNamespaceObject) {
push(@implContent, " JSC_TO_STRING_TAG_WITHOUT_TRANSITION();\n");
push(@implContent, "}\n\n");
}
# - Initialize static ClassInfo object
push(@implContent, "const ClassInfo $className" . "::s_info = { \"${visibleInterfaceName}\", &Base::s_info, ");
if ($numInstanceProperties > 0) {
push(@implContent, "&${className}Table");
} else {
push(@implContent, "nullptr");
}
if ($interface->extendedAttributes->{DOMJIT}) {
push(@implContent, "\n");
push(@implContent, "#if ENABLE(JIT)\n");
push(@implContent, ", &checkSubClassSnippetFor${className}\n");
push(@implContent, "#else\n");
push(@implContent, ", nullptr\n");
push(@implContent, "#endif\n");
} else {
push(@implContent, ", nullptr");
}
push(@implContent, ", CREATE_METHOD_TABLE($className) };\n\n");
# Constructor
if ($interfaceName eq "DOMWindow" || $interfaceName eq "RemoteDOMWindow") {
AddIncludesForImplementationTypeInImpl("JSWindowProxy");
push(@implContent, "${className}::$className(VM& vm, Structure* structure, Ref<$implType>&& impl, JSWindowProxy* proxy)\n");
push(@implContent, " : $parentClassName(vm, structure, WTFMove(impl), proxy)\n");
push(@implContent, "{\n");
push(@implContent, "}\n\n");
} elsif ($codeGenerator->InheritsInterface($interface, "WorkerGlobalScope") || $codeGenerator->InheritsInterface($interface, "WorkletGlobalScope")) {
AddIncludesForImplementationTypeInImpl($interfaceName);
push(@implContent, "${className}::$className(VM& vm, Structure* structure, Ref<$implType>&& impl)\n");
push(@implContent, " : $parentClassName(vm, structure, WTFMove(impl))\n");
push(@implContent, "{\n");
push(@implContent, "}\n\n");
} elsif (!NeedsImplementationClass($interface)) {
push(@implContent, "${className}::$className(Structure* structure, JSDOMGlobalObject& globalObject)\n");
push(@implContent, " : $parentClassName(structure, globalObject) { }\n\n");
} else {
push(@implContent, "${className}::$className(Structure* structure, JSDOMGlobalObject& globalObject, Ref<$implType>&& impl)\n");
push(@implContent, " : $parentClassName(structure, globalObject, WTFMove(impl))\n");
push(@implContent, "{\n");
push(@implContent, "}\n\n");
}
# Finish Creation
if ($interfaceName eq "DOMWindow" || $interfaceName eq "RemoteDOMWindow") {
push(@implContent, "void ${className}::finishCreation(VM& vm, JSWindowProxy* proxy)\n");
push(@implContent, "{\n");
push(@implContent, " Base::finishCreation(vm, proxy);\n\n");
} elsif ($codeGenerator->InheritsInterface($interface, "WorkerGlobalScope") || $codeGenerator->InheritsInterface($interface, "WorkletGlobalScope")) {
push(@implContent, "void ${className}::finishCreation(VM& vm, JSProxy* proxy)\n");
push(@implContent, "{\n");
push(@implContent, " Base::finishCreation(vm, proxy);\n\n");
} else {
push(@implContent, "void ${className}::finishCreation(VM& vm)\n");
push(@implContent, "{\n");
push(@implContent, " Base::finishCreation(vm);\n");
push(@implContent, " ASSERT(inherits(vm, info()));\n\n");
}
if (!$codeGenerator->InheritsExtendedAttribute($interface, "JSBuiltin")) {
AddToImplIncludes("ActiveDOMObject.h");
if ($codeGenerator->InheritsExtendedAttribute($interface, "ActiveDOMObject")) {
push(@implContent, " static_assert(std::is_base_of<ActiveDOMObject, ${implType}>::value, \"Interface is marked as [ActiveDOMObject] but implementation class does not subclass ActiveDOMObject.\");\n\n");
} elsif (!$codeGenerator->InheritsExtendedAttribute($interface, "CustomIsReachable")) {
push(@implContent, " static_assert(!std::is_base_of<ActiveDOMObject, ${implType}>::value, \"Interface is not marked as [ActiveDOMObject] even though implementation class subclasses ActiveDOMObject.\");\n\n");
}
}
if ($interfaceName eq "Location") {
push(@implContent, " putDirect(vm, vm.propertyNames->valueOf, globalObject()->objectProtoValueOfFunction(), JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);\n");
push(@implContent, " putDirect(vm, vm.propertyNames->toPrimitiveSymbol, jsUndefined(), JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);\n");
}
# Support for RuntimeEnabled attributes on instances.
foreach my $attribute (@{$interface->attributes}) {
next unless NeedsRuntimeCheck($interface, $attribute);
next unless AttributeShouldBeOnInstance($interface, $attribute);
AddToImplIncludes("WebCoreJSClientData.h");
my $runtimeEnableConditionalString = GenerateRuntimeEnableConditionalString($interface, $attribute, "globalObject()");
my $attributeName = $attribute->name;
my $getter = GetAttributeGetterName($interface, $className, $attribute);
my $setter = IsReadonly($attribute) || $codeGenerator->IsConstructorType($attribute->type) ? "nullptr" : GetAttributeSetterName($interface, $className, $attribute);
my $jscAttributes = GetJSCAttributesForAttribute($interface, $attribute);
my $isPrivateAndPublic = $attribute->extendedAttributes->{PublicIdentifier} && $attribute->extendedAttributes->{PrivateIdentifier};
my $conditionalString = $codeGenerator->GenerateConditionalString($attribute);
push(@implContent, "#if ${conditionalString}\n") if $conditionalString;
push(@implContent, " if (${runtimeEnableConditionalString})");
push(@implContent, " {") if $isPrivateAndPublic;
push(@implContent, "\n");
assert("CustomGetterSetter is not allowed for DOMAttribute. DOMAttributeGetterSetter must be used.") if IsAcceleratedDOMAttribute($interface, $attribute);
if ($attribute->extendedAttributes->{PublicIdentifier} || !$attribute->extendedAttributes->{PrivateIdentifier}) {
push(@implContent, " putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames()." . $attributeName . "PublicName(), CustomGetterSetter::create(vm, $getter, $setter), attributesForStructure($jscAttributes));\n");
}
if ($attribute->extendedAttributes->{PrivateIdentifier}) {
push(@implContent, " putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames()." . $attributeName . "PrivateName(), CustomGetterSetter::create(vm, $getter, $setter), attributesForStructure($jscAttributes));\n");
}
push(@implContent, " }\n") if $isPrivateAndPublic;
push(@implContent, "#endif\n") if $conditionalString;
}
# Support PrivateIdentifier attributes on instances.
foreach my $attribute (@{$interface->attributes}) {
next if NeedsRuntimeCheck($interface, $attribute);
next unless $attribute->extendedAttributes->{PrivateIdentifier};
next unless AttributeShouldBeOnInstance($interface, $attribute);
AddToImplIncludes("WebCoreJSClientData.h");
my $conditionalString = $codeGenerator->GenerateConditionalString($attribute);
my $attributeName = $attribute->name;
my $getter = GetAttributeGetterName($interface, $className, $attribute);
push(@implContent, "#if ${conditionalString}\n") if $conditionalString;
assert("CustomGetterSetter is not allowed for DOMAttribute. DOMAttributeGetterSetter must be used.") if IsAcceleratedDOMAttribute($interface, $attribute);
push(@implContent, " putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames()." . $attributeName . "PrivateName(), CustomGetterSetter::create(vm, $getter, nullptr), attributesForStructure(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly));\n");
push(@implContent, "#endif\n") if $conditionalString;
}
# Support for RuntimeEnabled operations on instances.
foreach my $operation (@{$interface->operations}) {
next unless NeedsRuntimeCheck($interface, $operation);
next unless OperationShouldBeOnInstance($interface, $operation);
next if $operation->{overloadIndex} && $operation->{overloadIndex} > 1;
AddToImplIncludes("WebCoreJSClientData.h");
my $runtimeEnableConditionalString = GenerateRuntimeEnableConditionalString($interface, $operation, "globalObject()");
my $functionName = $operation->name;
my $implementationFunction = GetFunctionName($interface, $className, $operation);
my $functionLength = GetFunctionLength($operation);
my $jsAttributes = ComputeFunctionSpecial($interface, $operation);
my $conditionalString = $codeGenerator->GenerateConditionalString($operation);
push(@implContent, "#if ${conditionalString}\n") if $conditionalString;
push(@implContent, " if (${runtimeEnableConditionalString})\n");
my $propertyName = "static_cast<JSVMClientData*>(vm.clientData)->builtinNames()." . $functionName . ($operation->extendedAttributes->{PrivateIdentifier} ? "PrivateName()" : "PublicName()");
if (IsJSBuiltin($interface, $operation)) {
push(@implContent, " putDirectBuiltinFunction(vm, this, $propertyName, $implementationFunction(vm), attributesForStructure($jsAttributes));\n");
} else {
push(@implContent, " putDirectNativeFunction(vm, this, $propertyName, $functionLength, $implementationFunction, NoIntrinsic, attributesForStructure($jsAttributes));\n");
}
push(@implContent, "#endif\n") if $conditionalString;
}
push(@implContent, " vm.heap.reportExtraMemoryAllocated(wrapped().memoryCost());\n") if $interface->extendedAttributes->{ReportExtraMemoryCost};
push(@implContent, "}\n\n");
unless (ShouldUseGlobalObjectPrototype($interface) || $interface->isNamespaceObject) {
push(@implContent, "JSObject* ${className}::createPrototype(VM& vm, JSDOMGlobalObject& globalObject)\n");
push(@implContent, "{\n");
if ($interface->parentType) {
my $parentClassNameForPrototype = "JS" . $interface->parentType->name;
push(@implContent, " return ${className}Prototype::create(vm, &globalObject, ${className}Prototype::createStructure(vm, &globalObject, ${parentClassNameForPrototype}::prototype(vm, globalObject)));\n");
} else {
my $prototype = $interface->extendedAttributes->{Exception} ? "errorPrototype" : "objectPrototype";
push(@implContent, " return ${className}Prototype::create(vm, &globalObject, ${className}Prototype::createStructure(vm, &globalObject, globalObject.${prototype}()));\n");
}
push(@implContent, "}\n\n");
push(@implContent, "JSObject* ${className}::prototype(VM& vm, JSDOMGlobalObject& globalObject)\n");
push(@implContent, "{\n");
push(@implContent, " return getDOMPrototype<${className}>(vm, globalObject);\n");
push(@implContent, "}\n\n");
}
if (!$interface->extendedAttributes->{LegacyNoInterfaceObject}) {
AddToImplIncludes("JSDOMGlobalObjectInlines.h");
push(@implContent, "JSValue ${className}::getConstructor(VM& vm, const JSGlobalObject* globalObject)\n");
push(@implContent, "{\n");
push(@implContent, " return getDOMConstructor<${className}DOMConstructor, DOMConstructorID::${interfaceName}>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject));\n");
push(@implContent, "}\n\n");
if ($interface->extendedAttributes->{LegacyFactoryFunction}) {
push(@implContent, "JSValue ${className}::getLegacyFactoryFunction(VM& vm, JSGlobalObject* globalObject)\n");
push(@implContent, "{\n");
push(@implContent, " return getDOMConstructor<${className}LegacyFactoryFunction, DOMConstructorID::${interfaceName}LegacyFactory>(vm, *jsCast<JSDOMGlobalObject*>(globalObject));\n");
push(@implContent, "}\n\n");
}
}
if (!$hasParent) {
push(@implContent, "void ${className}::destroy(JSC::JSCell* cell)\n");
push(@implContent, "{\n");
push(@implContent, " ${className}* thisObject = static_cast<${className}*>(cell);\n");
push(@implContent, " thisObject->${className}::~${className}();\n");
push(@implContent, "}\n\n");
}
if (InstanceOverridesGetOwnPropertySlot($interface)) {
GenerateGetOwnPropertySlot(\@implContent, $interface, $className);
GenerateGetOwnPropertySlotByIndex(\@implContent, $interface, $className);
}
if (InstanceOverridesGetOwnPropertyNames($interface)) {
GenerateGetOwnPropertyNames(\@implContent, $interface, $className);
}
if (InstanceOverridesPut($interface)) {
GeneratePut(\@implContent, $interface, $className);
GeneratePutByIndex(\@implContent, $interface, $className);
}
if (InstanceOverridesDefineOwnProperty($interface)) {
GenerateDefineOwnProperty(\@implContent, $interface, $className);
}
if (InstanceOverridesDeleteProperty($interface)) {
GenerateNamedDeleterDefinition(\@implContent, $interface, $className);
}
if (InstanceOverridesGetCallData($interface)) {
GenerateGetCallData(\@implContent, $interface, $className);
}
AddToImplIncludes("JSDOMAttribute.h") if $numAttributes > 0;
AddToImplIncludes("JSDOMOperation.h") if $numOperations > 0 && $interfaceName ne "EventTarget";
if (NeedsConstructorProperty($interface)) {
my $constructorGetter = "js" . $interfaceName . "Constructor";
push(@implContent, "JSC_DEFINE_CUSTOM_GETTER(${constructorGetter}, (JSGlobalObject* lexicalGlobalObject, EncodedJSValue thisValue, PropertyName))\n");
push(@implContent, "{\n");
push(@implContent, " VM& vm = JSC::getVM(lexicalGlobalObject);\n");
push(@implContent, " auto throwScope = DECLARE_THROW_SCOPE(vm);\n");
push(@implContent, " auto* prototype = jsDynamicCast<${className}Prototype*>(vm, JSValue::decode(thisValue));\n");
push(@implContent, " if (UNLIKELY(!prototype))\n");
push(@implContent, " return throwVMTypeError(lexicalGlobalObject, throwScope);\n");
push(@implContent, " return JSValue::encode(${className}::getConstructor(JSC::getVM(lexicalGlobalObject), prototype->globalObject()));\n");
push(@implContent, "}\n\n");
}
foreach my $attribute (@{$interface->extendedAttributes->{SyntheticIDLAttributes}}, @{$interface->attributes}) {
GenerateAttributeGetterDefinition(\@implContent, $interface, $className, $attribute);
GenerateAttributeSetterDefinition(\@implContent, $interface, $className, $attribute);
}
foreach my $operation (@{$interface->operations}) {
GenerateOperationDefinition(\@implContent, $interface, $className, $operation);
}
GenerateIterableDefinition($interface) if $interface->iterable;
AddToImplIncludes("DOMIsoSubspaces.h");
AddToImplIncludes("WebCoreJSClientData.h");
AddToImplIncludes("<JavaScriptCore/JSDestructibleObjectHeapCellType.h>");
AddToImplIncludes("<JavaScriptCore/SlotVisitorMacros.h>");
AddToImplIncludes("<JavaScriptCore/SubspaceInlines.h>");
push(@implContent, "JSC::IsoSubspace* ${className}::subspaceForImpl(JSC::VM& vm)\n");
push(@implContent, "{\n");
push(@implContent, " auto& clientData = *static_cast<JSVMClientData*>(vm.clientData);\n");
push(@implContent, " auto& spaces = clientData.subspaces();\n");
push(@implContent, " if (auto* space = spaces.m_subspaceFor${interfaceName}.get())\n");
push(@implContent, " return space;\n");
if (IsDOMGlobalObject($interface)) {
push(@implContent, " spaces.m_subspaceFor${interfaceName} = makeUnique<IsoSubspace> ISO_SUBSPACE_INIT(vm.heap, clientData.m_heapCellTypeFor${className}.get(), ${className});\n");
} else {
push(@implContent, " static_assert(std::is_base_of_v<JSC::JSDestructibleObject, ${className}> || !${className}::needsDestruction);\n");
push(@implContent, " if constexpr (std::is_base_of_v<JSC::JSDestructibleObject, ${className}>)\n");
push(@implContent, " spaces.m_subspaceFor${interfaceName} = makeUnique<IsoSubspace> ISO_SUBSPACE_INIT(vm.heap, vm.destructibleObjectHeapCellType.get(), ${className});\n");
push(@implContent, " else\n");
push(@implContent, " spaces.m_subspaceFor${interfaceName} = makeUnique<IsoSubspace> ISO_SUBSPACE_INIT(vm.heap, vm.cellHeapCellType.get(), ${className});\n");
}
push(@implContent, " auto* space = spaces.m_subspaceFor${interfaceName}.get();\n");
push(@implContent, "IGNORE_WARNINGS_BEGIN(\"unreachable-code\")\n");
push(@implContent, "IGNORE_WARNINGS_BEGIN(\"tautological-compare\")\n");
push(@implContent, " void (*myVisitOutputConstraint)(JSC::JSCell*, JSC::SlotVisitor&) = ${className}::visitOutputConstraints;\n");
push(@implContent, " void (*jsCellVisitOutputConstraint)(JSC::JSCell*, JSC::SlotVisitor&) = JSC::JSCell::visitOutputConstraints;\n");
push(@implContent, " if (myVisitOutputConstraint != jsCellVisitOutputConstraint)\n");
# push(@implContent, " if (&${className}::visitOutputConstraints != &JSC::JSCell::visitOutputConstraints)\n");
push(@implContent, " clientData.outputConstraintSpaces().append(space);\n");
push(@implContent, "IGNORE_WARNINGS_END\n");
push(@implContent, "IGNORE_WARNINGS_END\n");
push(@implContent, " return space;\n");
push(@implContent, "}\n\n");
if ($needsVisitChildren) {
push(@implContent, "template<typename Visitor>\n");
push(@implContent, "void ${className}::visitChildrenImpl(JSCell* cell, Visitor& visitor)\n");
push(@implContent, "{\n");
push(@implContent, " auto* thisObject = jsCast<${className}*>(cell);\n");
push(@implContent, " ASSERT_GC_OBJECT_INHERITS(thisObject, info());\n");
push(@implContent, " Base::visitChildren(thisObject, visitor);\n");
push(@implContent, " thisObject->visitAdditionalChildren(visitor);\n") if $interface->extendedAttributes->{JSCustomMarkFunction};
if ($interface->extendedAttributes->{Plugin}) {
push(@implContent, "#if PLATFORM(COCOA)\n");
push(@implContent, " thisObject->wrapped().pluginReplacementScriptObject().visit(visitor);\n");
push(@implContent, "#endif\n");
}
if ($interface->extendedAttributes->{GenerateAddOpaqueRoot}) {
AddToImplIncludes("<wtf/GetPtr.h>");
my $functionName = $interface->extendedAttributes->{GenerateAddOpaqueRoot};
$functionName = "opaqueRoot" if $functionName eq "VALUE_IS_MISSING";
push(@implContent, " visitor.addOpaqueRoot(WTF::getPtr(thisObject->wrapped().${functionName}()));\n");
}
if ($interface->extendedAttributes->{ReportExtraMemoryCost}) {
push(@implContent, " visitor.reportExtraMemoryVisited(thisObject->wrapped().memoryCost());\n");
if ($interface->extendedAttributes->{ReportExternalMemoryCost}) {;
push(@implContent, "#if ENABLE(RESOURCE_USAGE)\n");
push(@implContent, " visitor.reportExternalMemoryVisited(thisObject->wrapped().externalMemoryCost());\n");
push(@implContent, "#endif\n");
}
}
if ($numCachedAttributes > 0) {
foreach my $attribute (@{$interface->attributes}) {
if ($attribute->extendedAttributes->{CachedAttribute}) {
my $conditionalString = $codeGenerator->GenerateConditionalString($attribute);
push(@implContent, "#if ${conditionalString}\n") if $conditionalString;
push(@implContent, " visitor.append(thisObject->m_" . $attribute->name . ");\n");
push(@implContent, "#endif\n") if $conditionalString;
}
}
}
push(@implContent, "}\n\n");
push(@implContent, "DEFINE_VISIT_CHILDREN(${className});\n\n");
if ($interface->extendedAttributes->{JSCustomMarkFunction}) {
push(@implContent, "template<typename Visitor>\n");
push(@implContent, "void ${className}::visitOutputConstraints(JSCell* cell, Visitor& visitor)\n");
push(@implContent, "{\n");
push(@implContent, " auto* thisObject = jsCast<${className}*>(cell);\n");
push(@implContent, " ASSERT_GC_OBJECT_INHERITS(thisObject, info());\n");
push(@implContent, " Base::visitOutputConstraints(thisObject, visitor);\n");
push(@implContent, " thisObject->visitAdditionalChildren(visitor);\n");
push(@implContent, "}\n\n");
push(@implContent, "template void ${className}::visitOutputConstraints(JSCell*, AbstractSlotVisitor&);\n");
push(@implContent, "template void ${className}::visitOutputConstraints(JSCell*, SlotVisitor&);\n");
}
}
if (InstanceNeedsEstimatedSize($interface)) {
push(@implContent, "size_t ${className}::estimatedSize(JSCell* cell, VM& vm)\n");
push(@implContent, "{\n");
push(@implContent, " auto* thisObject = jsCast<${className}*>(cell);\n");
push(@implContent, " return Base::estimatedSize(thisObject, vm) + thisObject->wrapped().memoryCost();\n");
push(@implContent, "}\n\n");
}
if (NeedsImplementationClass($interface) && !$interface->extendedAttributes->{CustomHeapSnapshot}) {
AddToImplIncludes("<JavaScriptCore/HeapAnalyzer.h>");
AddToImplIncludes("ScriptExecutionContext.h");
AddToImplIncludes("<wtf/URL.h>");
push(@implContent, "void ${className}::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)\n");
push(@implContent, "{\n");
push(@implContent, " auto* thisObject = jsCast<${className}*>(cell);\n");
push(@implContent, " analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped());\n");
push(@implContent, " if (thisObject->scriptExecutionContext())\n");
push(@implContent, " analyzer.setLabelForCell(cell, \"url \" + thisObject->scriptExecutionContext()->url().string());\n");
push(@implContent, " Base::analyzeHeap(cell, analyzer);\n");
push(@implContent, "}\n\n");
}
if ($indexedGetterOperation) {
$implIncludes{"<wtf/URL.h>"} = 1 if $indexedGetterOperation->type->name eq "DOMString";
if ($interfaceName =~ /^HTML\w*Collection$/ or $interfaceName eq "RadioNodeList") {
$implIncludes{"JSNode.h"} = 1;
$implIncludes{"Node.h"} = 1;
}
}
if (ShouldGenerateWrapperOwnerCode($hasParent, $interface) && !GetCustomIsReachable($interface)) {
push(@implContent, "bool JS${interfaceName}Owner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, AbstractSlotVisitor& visitor, const char** reason)\n");
push(@implContent, "{\n");
# All ActiveDOMObjects implement hasPendingActivity(), but not all of them
# increment their C++ reference counts when hasPendingActivity() becomes
# true. As a result, ActiveDOMObjects can be prematurely destroyed before
# their pending activities complete. To wallpaper over this bug, JavaScript
# wrappers unconditionally keep ActiveDOMObjects with pending activity alive.
# FIXME: Fix this lifetime issue in the DOM, and move this hasPendingActivity
# check just above the (GetGenerateIsReachable($interface) eq "Impl") check below.
my $emittedJSCast = 0;
if ($codeGenerator->InheritsExtendedAttribute($interface, "ActiveDOMObject")) {
push(@implContent, " auto* js${interfaceName} = jsCast<JS${interfaceName}*>(handle.slot()->asCell());\n");
$emittedJSCast = 1;
push(@implContent, " auto& wrapped = js${interfaceName}->wrapped();\n");
push(@implContent, " if (!wrapped.isContextStopped() && wrapped.hasPendingActivity()) {\n");
push(@implContent, " if (UNLIKELY(reason))\n");
push(@implContent, " *reason = \"ActiveDOMObject with pending activity\";\n");
push(@implContent, " return true;\n");
push(@implContent, " }\n");
}
if ($codeGenerator->InheritsInterface($interface, "EventTarget")) {
if (!$emittedJSCast) {
push(@implContent, " auto* js${interfaceName} = jsCast<JS${interfaceName}*>(handle.slot()->asCell());\n");
$emittedJSCast = 1;
}
push(@implContent, " if (js${interfaceName}->wrapped().isFiringEventListeners()) {\n");
push(@implContent, " if (UNLIKELY(reason))\n");
push(@implContent, " *reason = \"EventTarget firing event listeners\";\n");
push(@implContent, " return true;\n");
push(@implContent, " }\n");
}
if ($codeGenerator->InheritsInterface($interface, "Node")) {
if (!$emittedJSCast) {
push(@implContent, " auto* js${interfaceName} = jsCast<JS${interfaceName}*>(handle.slot()->asCell());\n");
$emittedJSCast = 1;
}
push(@implContent, " if (JSNodeOwner::isReachableFromOpaqueRoots(handle, 0, visitor, reason))\n");
push(@implContent, " return true;\n");
}
if (GetGenerateIsReachable($interface)) {
if (!$emittedJSCast) {
push(@implContent, " auto* js${interfaceName} = jsCast<JS${interfaceName}*>(handle.slot()->asCell());\n");
$emittedJSCast = 1;
}
my $rootString;
if (GetGenerateIsReachable($interface) eq "Impl") {
$rootString = " ${implType}* root = &js${interfaceName}->wrapped();\n";
$rootString .= " if (UNLIKELY(reason))\n";
$rootString .= " *reason = \"Reachable from ${interfaceName}\";\n";
} elsif (GetGenerateIsReachable($interface) eq "ImplWebGLRenderingContext") {
$rootString = " WebGLRenderingContextBase* root = WTF::getPtr(js${interfaceName}->wrapped().context());\n";
$rootString .= " if (UNLIKELY(reason))\n";
$rootString .= " *reason = \"Reachable from ${interfaceName}\";\n";
} elsif (GetGenerateIsReachable($interface) eq "ReachableFromDOMWindow") {
$rootString = " auto* root = WTF::getPtr(js${interfaceName}->wrapped().window());\n";
$rootString .= " if (!root)\n";
$rootString .= " return false;\n";
$rootString .= " if (UNLIKELY(reason))\n";
$rootString .= " *reason = \"Reachable from Window\";\n";
} elsif (GetGenerateIsReachable($interface) eq "ReachableFromNavigator") {
$implIncludes{"Navigator.h"} = 1;
$implIncludes{"WorkerNavigator.h"} = 1;
$rootString = " NavigatorBase* root = WTF::getPtr(js${interfaceName}->wrapped().navigator());\n";
$rootString .= " if (!root)\n";
$rootString .= " return false;\n";
$rootString .= " if (UNLIKELY(reason))\n";
$rootString .= " *reason = \"Reachable from Navigator\";\n";
} elsif (GetGenerateIsReachable($interface) eq "ImplCanvasBase") {
$rootString = " CanvasBase* root = WTF::getPtr(&(js${interfaceName}->wrapped().canvasBase()));\n";
$rootString .= " if (!root)\n";
$rootString .= " return false;\n";
$rootString .= " if (UNLIKELY(reason))\n";
$rootString .= " *reason = \"Reachable from CanvasBase\";\n";
} elsif (GetGenerateIsReachable($interface) eq "ImplDocument") {
$rootString = " Document* root = WTF::getPtr(js${interfaceName}->wrapped().document());\n";
$rootString .= " if (!root)\n";
$rootString .= " return false;\n";
$rootString .= " if (UNLIKELY(reason))\n";
$rootString .= " *reason = \"Reachable from Document\";\n";
} elsif (GetGenerateIsReachable($interface) eq "ImplElementRoot") {
$implIncludes{"Element.h"} = 1;
$implIncludes{"JSNodeCustom.h"} = 1;
$rootString = " Element* element = WTF::getPtr(js${interfaceName}->wrapped().element());\n";
$rootString .= " if (!element)\n";
$rootString .= " return false;\n";
$rootString .= " if (UNLIKELY(reason))\n";
$rootString .= " *reason = \"Reachable from ${interfaceName}Owner\";\n";
$rootString .= " void* root = WebCore::root(element);\n";
} elsif (GetGenerateIsReachable($interface) eq "ImplOwnerNodeRoot") {
$implIncludes{"Element.h"} = 1;
$implIncludes{"JSNodeCustom.h"} = 1;
$rootString = " void* root = WebCore::root(js${interfaceName}->wrapped().ownerNode());\n";
$rootString .= " if (UNLIKELY(reason))\n";
$rootString .= " *reason = \"Reachable from ${interfaceName} ownerNode\";\n";
} elsif (GetGenerateIsReachable($interface) eq "ImplScriptExecutionContext") {
$rootString = " ScriptExecutionContext* root = WTF::getPtr(js${interfaceName}->wrapped().scriptExecutionContext());\n";
$rootString .= " if (!root)\n";
$rootString .= " return false;\n";
$rootString .= " if (UNLIKELY(reason))\n";
$rootString .= " *reason = \"Reachable from ScriptExecutionContext\";\n";
} elsif (GetGenerateIsReachable($interface) eq "ImplWebXRSessionRoot") {
$rootString = " WebXRSession* root = WTF::getPtr(js${interfaceName}->wrapped().session());\n";
$rootString .= " if (!root)\n";
$rootString .= " return false;\n";
$rootString .= " if (UNLIKELY(reason))\n";
$rootString .= " *reason = \"Reachable from WebXRSession\";\n";
} else {
$rootString = " void* root = WebCore::root(&js${interfaceName}->wrapped());\n";
$rootString .= " if (UNLIKELY(reason))\n";
$rootString .= " *reason = \"Reachable from js${interfaceName}\";\n";
}
push(@implContent, $rootString);
push(@implContent, " return visitor.containsOpaqueRoot(root);\n");
} else {
if (!$emittedJSCast) {
push(@implContent, " UNUSED_PARAM(handle);\n");
}
push(@implContent, " UNUSED_PARAM(visitor);\n");
push(@implContent, " UNUSED_PARAM(reason);\n");
push(@implContent, " return false;\n");
}
push(@implContent, "}\n\n");
}
if (ShouldGenerateWrapperOwnerCode($hasParent, $interface) && !$interface->extendedAttributes->{JSCustomFinalize}) {
push(@implContent, "void JS${interfaceName}Owner::finalize(JSC::Handle<JSC::Unknown> handle, void* context)\n");
push(@implContent, "{\n");
push(@implContent, " auto* js${interfaceName} = static_cast<JS${interfaceName}*>(handle.slot()->asCell());\n");
push(@implContent, " auto& world = *static_cast<DOMWrapperWorld*>(context);\n");
push(@implContent, " uncacheWrapper(world, &js${interfaceName}->wrapped(), js${interfaceName});\n");
push(@implContent, "}\n\n");
}
if (ShouldGenerateToJSImplementation($hasParent, $interface)) {
my $vtableNameGnu = GetGnuVTableNameForInterface($interface);
my $vtableRefGnu = GetGnuVTableRefForInterface($interface);
my $vtableRefWin = GetWinVTableRefForInterface($interface);
push(@implContent, <<END) if $vtableNameGnu;
#if ENABLE(BINDING_INTEGRITY)
#if PLATFORM(WIN)
#pragma warning(disable: 4483)
extern "C" { extern void (*const ${vtableRefWin}[])(); }
#else
extern "C" { extern void* ${vtableNameGnu}[]; }
#endif
#endif
END
push(@implContent, "JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject* globalObject, Ref<$implType>&& impl)\n");
push(@implContent, "{\n");
push(@implContent, <<END) if $vtableNameGnu;
#if ENABLE(BINDING_INTEGRITY)
const void* actualVTablePointer = getVTablePointer(impl.ptr());
#if PLATFORM(WIN)
void* expectedVTablePointer = ${vtableRefWin};
#else
void* expectedVTablePointer = ${vtableRefGnu};
#endif
// If this fails ${implType} does not have a vtable, so you need to add the
// ImplementationLacksVTable attribute to the interface definition
static_assert(std::is_polymorphic<${implType}>::value, "${implType} is not polymorphic");
// If you hit this assertion you either have a use after free bug, or
// ${implType} has subclasses. If ${implType} has subclasses that get passed
// to toJS() we currently require $interfaceName you to opt out of binding hardening
// by adding the SkipVTableValidation attribute to the interface IDL definition
RELEASE_ASSERT(actualVTablePointer == expectedVTablePointer);
#endif
END
push(@implContent, <<END) if $interface->extendedAttributes->{ImplementationLacksVTable};
// If you hit this failure the interface definition has the ImplementationLacksVTable
// attribute. You should remove that attribute. If the class has subclasses
// that may be passed through this toJS() function you should use the SkipVTableValidation
// attribute to $interfaceName.
static_assert(!std::is_polymorphic<${implType}>::value, "${implType} is polymorphic but the IDL claims it is not");
END
push(@implContent, " return createWrapper<${implType}>(globalObject, WTFMove(impl));\n");
push(@implContent, "}\n\n");
push(@implContent, "JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, ${implType}& impl)\n");
push(@implContent, "{\n");
push(@implContent, " return wrap(lexicalGlobalObject, globalObject, impl);\n");
push(@implContent, "}\n\n");
}
if (ShouldGenerateToWrapped($hasParent, $interface) and !$interface->extendedAttributes->{JSCustomToNativeObject}) {
push(@implContent, "${implType}* ${className}::toWrapped(JSC::VM& vm, JSC::JSValue value)\n");
push(@implContent, "{\n");
push(@implContent, " if (auto* wrapper = " . GetCastingHelperForThisObject($interface) . "(vm, value))\n");
push(@implContent, " return &wrapper->wrapped();\n");
push(@implContent, " return nullptr;\n");
push(@implContent, "}\n");
}
push(@implContent, "\n}\n");
my $conditionalString = $codeGenerator->GenerateConditionalString($interface);
push(@implContent, "\n#endif // ${conditionalString}\n") if $conditionalString;
}
sub GenerateAttributeGetterAndSetterDeclaration
{
my ($outputArray, $interface, $className, $attribute) = @_;
return if IsJSBuiltin($interface, $attribute);
return if $attribute->extendedAttributes->{DelegateToSharedSyntheticAttribute};
my $conditionalString = $codeGenerator->GenerateConditionalString($attribute);
push(@$outputArray, "#if ${conditionalString}\n") if $conditionalString;
my $getter = GetAttributeGetterName($interface, $className, $attribute);
push(@$outputArray, "static JSC_DECLARE_CUSTOM_GETTER(${getter});\n");
unless (IsReadonly($attribute) || $codeGenerator->IsConstructorType($attribute->type)) {
my $readWriteConditional = $attribute->extendedAttributes->{ConditionallyReadWrite};
if ($readWriteConditional) {
my $readWriteConditionalString = $codeGenerator->GenerateConditionalStringFromAttributeValue($readWriteConditional);
push(@$outputArray, "#if ${readWriteConditionalString}\n");
}
my $setter = GetAttributeSetterName($interface, $className, $attribute);
push(@$outputArray, "static JSC_DECLARE_CUSTOM_SETTER(${setter});\n");
push(@$outputArray, "#endif\n") if $readWriteConditional;
}
push(@$outputArray, "#endif\n") if $conditionalString;
}
sub GenerateAttributeGetterBodyDefinition
{
my ($outputArray, $interface, $className, $attribute, $attributeGetterBodyName, $conditional) = @_;
my @signatureArguments = ();
push(@signatureArguments, "JSGlobalObject& lexicalGlobalObject");
push(@signatureArguments, "${className}& thisObject") if !$attribute->isStatic;
push(@signatureArguments, "PropertyName propertyName") if $codeGenerator->ExtendedAttributeContains($attribute->extendedAttributes->{CallWith}, "PropertyName");
my $needSecurityCheck = $interface->extendedAttributes->{CheckSecurity} && !$attribute->extendedAttributes->{DoNotCheckSecurity} && !$attribute->extendedAttributes->{DoNotCheckSecurityOnGetter};
my $hasCustomGetter = HasCustomGetter($attribute);
my $isEventHandler = $attribute->type->name eq "EventHandler";
my $isConstructor = $codeGenerator->IsConstructorType($attribute->type);
my $needThrowScope = $needSecurityCheck || (!$hasCustomGetter && !$isEventHandler && !$isConstructor);
push(@$outputArray, "static inline JSValue ${attributeGetterBodyName}(" . join(", ", @signatureArguments) . ")\n");
push(@$outputArray, "{\n");
if ($needThrowScope) {
push(@$outputArray, " auto& vm = JSC::getVM(&lexicalGlobalObject);\n");
push(@$outputArray, " auto throwScope = DECLARE_THROW_SCOPE(vm);\n");
} else {
push(@$outputArray, " UNUSED_PARAM(lexicalGlobalObject);\n");
}
if ($needSecurityCheck) {
if ($interface->type->name eq "DOMWindow") {
AddToImplIncludes("JSDOMBindingSecurityInlines.h", $conditional);
push(@$outputArray, " bool shouldAllowAccess = BindingSecurity::shouldAllowAccessToDOMWindow(&lexicalGlobalObject, thisObject, ThrowSecurityError);\n");
} else {
AddToImplIncludes("JSDOMBindingSecurity.h", $conditional);
push(@$outputArray, " bool shouldAllowAccess = BindingSecurity::shouldAllowAccessToDOMWindow(&lexicalGlobalObject, thisObject.wrapped().window(), ThrowSecurityError);\n");
}
push(@$outputArray, " EXCEPTION_ASSERT_UNUSED(throwScope, !throwScope.exception() || !shouldAllowAccess);\n");
push(@$outputArray, " if (!shouldAllowAccess)\n");
push(@$outputArray, " return jsUndefined();\n");
}
if ($hasCustomGetter) {
my $implGetterFunctionName = $codeGenerator->WK_lcfirst($attribute->extendedAttributes->{ImplementedAs} || $attribute->name);
push(@$outputArray, " return thisObject.${implGetterFunctionName}(lexicalGlobalObject);\n");
} elsif ($isEventHandler) {
$implIncludes{"EventNames.h"} = 1;
my $getter = $attribute->extendedAttributes->{WindowEventHandler} ? "windowEventHandlerAttribute"
: $attribute->extendedAttributes->{DocumentEventHandler} ? "documentEventHandlerAttribute"
: "eventHandlerAttribute";
my $eventName = EventHandlerAttributeEventName($attribute);
push(@$outputArray, " return $getter(thisObject.wrapped(), $eventName, worldForDOMObject(thisObject));\n");
} elsif ($isConstructor) {
# FIXME: This should be switched to using an extended attribute rather than infering this information from name.
my $constructorType = $attribute->type->name;
my $constructorGetter = ($constructorType =~ /LegacyFactoryFunctionConstructor$/) ? "getLegacyFactoryFunction" : "getConstructor";
# Strip off any trailing "Constructor" or "LegacyFactoryFunctionConstructor" to get the real type.
$constructorType =~ s/Constructor$//;
# $constructorType ~= /LegacyFactoryFunction$/ indicates that it is LegacyFactoryFunction.
# We do not generate the header file for LegacyFactoryFunction of class X,
# since we generate the LegacyFactoryFunction declaration into the header file of class X.
if ($constructorType ne "any" and $constructorType !~ /LegacyFactoryFunction$/) {
AddToImplIncludes("JS" . $constructorType . ".h", $conditional);
}
$constructorType =~ s/LegacyFactoryFunction$//;
if (IsDOMGlobalObject($interface)) {
push(@$outputArray, " return JS" . $constructorType . "::${constructorGetter}(JSC::getVM(&lexicalGlobalObject), &thisObject);\n");
} else {
push(@$outputArray, " return JS" . $constructorType . "::${constructorGetter}(JSC::getVM(&lexicalGlobalObject), thisObject.globalObject());\n");
}
} else {
if ($attribute->extendedAttributes->{CachedAttribute}) {
push(@$outputArray, " if (JSValue cachedValue = thisObject.m_" . $attribute->name . ".get())\n");
push(@$outputArray, " return cachedValue;\n");
}
my @callWithArgs = GenerateCallWithUsingReferences($attribute->extendedAttributes->{CallWith}, $outputArray, "jsUndefined()", "thisObject");
my ($baseFunctionName, @arguments) = $codeGenerator->GetterExpression(\%implIncludes, $interface->type->name, $attribute);
my $functionName = GetFullyQualifiedImplementationCallName($interface, $attribute, $baseFunctionName, "impl", $conditional);
AddAdditionalArgumentsForImplementationCall(\@arguments, $interface, $attribute, "impl", "lexicalGlobalObject", "", "thisObject");
unshift(@arguments, @callWithArgs);
my $globalObjectReference = $attribute->isStatic ? "*jsCast<JSDOMGlobalObject*>(&lexicalGlobalObject)" : "*thisObject.globalObject()";
my $toJSExpression = NativeToJSValueUsingReferences($attribute, $interface, "${functionName}(" . join(", ", @arguments) . ")", $globalObjectReference);
push(@$outputArray, " auto& impl = thisObject.wrapped();\n") unless $attribute->isStatic or $attribute->extendedAttributes->{ForwardToMapLike} or $attribute->extendedAttributes->{ForwardToSetLike};
if (!IsReadonly($attribute)) {
my $callTracer = $attribute->extendedAttributes->{CallTracer} || $interface->extendedAttributes->{CallTracer};
if ($callTracer) {
my @callTracerArguments = ();
GenerateCallTracer($outputArray, $callTracer, $attribute->name, \@callTracerArguments, " ");
}
}
if ($attribute->extendedAttributes->{CachedAttribute}) {
push(@$outputArray, " JSValue result = ${toJSExpression};\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, { });\n") if ($needThrowScope);
push(@$outputArray, " thisObject.m_" . $attribute->name . ".set(JSC::getVM(&lexicalGlobalObject), &thisObject, result);\n");
push(@$outputArray, " return result;\n");
} elsif ($needThrowScope) {
push(@$outputArray, " RELEASE_AND_RETURN(throwScope, (${toJSExpression}));\n");
} else {
push(@$outputArray, " return ${toJSExpression};\n");
}
}
push(@$outputArray, "}\n\n");
}
sub GenerateAttributeGetterTrampolineDefinition
{
my ($outputArray, $interface, $className, $attribute, $attributeGetterName, $attributeGetterBodyName, $conditional) = @_;
AddToImplIncludes("JSDOMAttribute.h", $conditional);
my $callAttributeGetterName = "get";
$callAttributeGetterName .= "Static" if $attribute->isStatic;
$callAttributeGetterName .= "PassingPropertyName" if $codeGenerator->ExtendedAttributeContains($attribute->extendedAttributes->{CallWith}, "PropertyName");
my @templateParameters = ();
push(@templateParameters, $attributeGetterBodyName);
if ($attribute->extendedAttributes->{LegacyLenientThis}) {
push(@templateParameters, "CastedThisErrorBehavior::ReturnEarly")
} elsif ($codeGenerator->IsPromiseType($attribute->type)) {
push(@templateParameters, "CastedThisErrorBehavior::RejectPromise")
} elsif (IsAcceleratedDOMAttribute($interface, $attribute)) {
push(@templateParameters, "CastedThisErrorBehavior::Assert");
}
push(@$outputArray, "JSC_DEFINE_CUSTOM_GETTER(${attributeGetterName}, (JSGlobalObject* lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))\n");
push(@$outputArray, "{\n");
push(@$outputArray, " return IDLAttribute<${className}>::${callAttributeGetterName}<" . join(", ", @templateParameters) . ">(*lexicalGlobalObject, thisValue, attributeName);\n");
push(@$outputArray, "}\n\n");
}
sub GenerateAttributeGetterDefinition
{
my ($outputArray, $interface, $className, $attribute) = @_;
return if IsJSBuiltin($interface, $attribute);
return if $attribute->extendedAttributes->{DelegateToSharedSyntheticAttribute};
my $attributeGetterName = GetAttributeGetterName($interface, $className, $attribute);
my $attributeGetterBodyName = $attributeGetterName . "Getter";
my $conditional = $attribute->extendedAttributes->{Conditional};
if ($conditional) {
my $conditionalString = $codeGenerator->GenerateConditionalStringFromAttributeValue($conditional);
push(@$outputArray, "#if ${conditionalString}\n");;
}
GenerateAttributeGetterBodyDefinition($outputArray, $interface, $className, $attribute, $attributeGetterBodyName, $conditional);
GenerateAttributeGetterTrampolineDefinition($outputArray, $interface, $className, $attribute, $attributeGetterName, $attributeGetterBodyName, $conditional);
push(@$outputArray, "#endif\n\n") if $conditional;
}
sub AttributeSetterNeedsPropertyName
{
my $attribute = shift;
return $codeGenerator->ExtendedAttributeContains($attribute->extendedAttributes->{CallWith}, "PropertyName")
|| $attribute->extendedAttributes->{Replaceable};
}
sub GenerateAttributeSetterBodyDefinition
{
my ($outputArray, $interface, $className, $attribute, $attributeSetterBodyName, $conditional) = @_;
my @signatureArguments = ();
push(@signatureArguments, "JSGlobalObject& lexicalGlobalObject");
push(@signatureArguments, "${className}& thisObject") if !$attribute->isStatic;
push(@signatureArguments, "JSValue value");
push(@signatureArguments, "PropertyName propertyName") if AttributeSetterNeedsPropertyName($attribute);
my $needSecurityCheck = $interface->extendedAttributes->{CheckSecurity} && !$attribute->extendedAttributes->{DoNotCheckSecurity} && !$attribute->extendedAttributes->{DoNotCheckSecurityOnSetter};
my $hasCustomSetter = HasCustomSetter($attribute);
my $isEventHandler = $attribute->type->name eq "EventHandler";
my $isReplaceable = $attribute->extendedAttributes->{Replaceable};
my $needThrowScope = $needSecurityCheck || (!$hasCustomSetter && !$isEventHandler && !$isReplaceable);
push(@$outputArray, "static inline bool ${attributeSetterBodyName}(" . join(", ", @signatureArguments) . ")\n");
push(@$outputArray, "{\n");
push(@$outputArray, " auto& vm = JSC::getVM(&lexicalGlobalObject);\n");
push(@$outputArray, " auto throwScope = DECLARE_THROW_SCOPE(vm);\n") if $needThrowScope;
GenerateCustomElementReactionsStackIfNeeded($outputArray, $attribute, "lexicalGlobalObject");
if ($needSecurityCheck) {
if ($interface->type->name eq "DOMWindow") {
AddToImplIncludes("JSDOMBindingSecurityInlines.h", $conditional);
push(@$outputArray, " bool shouldAllowAccess = BindingSecurity::shouldAllowAccessToDOMWindow(&lexicalGlobalObject, thisObject, ThrowSecurityError);\n");
} else {
AddToImplIncludes("JSDOMBindingSecurity.h", $conditional);
push(@$outputArray, " bool shouldAllowAccess = BindingSecurity::shouldAllowAccessToDOMWindow(&lexicalGlobalObject, thisObject.wrapped().window(), ThrowSecurityError);\n");
}
push(@$outputArray, " EXCEPTION_ASSERT_UNUSED(throwScope, !throwScope.exception() || !shouldAllowAccess);\n");
push(@$outputArray, " if (!shouldAllowAccess)\n");
push(@$outputArray, " return false;\n");
}
if ($hasCustomSetter) {
my $implSetterFunctionName = $codeGenerator->WK_ucfirst($attribute->name);
push(@$outputArray, " thisObject.set${implSetterFunctionName}(lexicalGlobalObject, value);\n");
push(@$outputArray, " return true;\n");
} elsif ($isEventHandler) {
AddToImplIncludes("JSEventListener.h", $conditional);
my $eventName = EventHandlerAttributeEventName($attribute);
# FIXME: Find a way to do this special case without hardcoding the class and attribute names here.
if (($interface->type->name eq "DOMWindow" or $interface->type->name eq "WorkerGlobalScope") and $attribute->name eq "onerror") {
AddToImplIncludes("JSErrorHandler.h", $conditional);
push(@$outputArray, " thisObject.wrapped().setAttributeEventListener($eventName, createJSErrorHandler(lexicalGlobalObject, value, thisObject), worldForDOMObject(thisObject));\n");
} else {
AddToImplIncludes("JSEventListener.h", $conditional);
my $setter = $attribute->extendedAttributes->{WindowEventHandler} ? "setWindowEventHandlerAttribute"
: $attribute->extendedAttributes->{DocumentEventHandler} ? "setDocumentEventHandlerAttribute"
: "setEventHandlerAttribute";
push(@$outputArray, " $setter(lexicalGlobalObject, thisObject, thisObject.wrapped(), ${eventName}, value);\n");
}
push(@$outputArray, " vm.heap.writeBarrier(&thisObject, value);\n");
push(@$outputArray, " ensureStillAliveHere(value);\n\n");
push(@$outputArray, " return true;\n");
} elsif ($isReplaceable) {
if ($needThrowScope) {
push(@$outputArray, " throwScope.release();\n");
} else {
push(@$outputArray, " UNUSED_PARAM(vm);\n");
}
push(@$outputArray, " bool shouldThrow = true;\n");
push(@$outputArray, " thisObject.createDataProperty(&lexicalGlobalObject, propertyName, value, shouldThrow);\n");
push(@$outputArray, " return true;\n");
} elsif ($attribute->extendedAttributes->{PutForwards}) {
assert("[PutForwards] is not compatible with static attributes") if $attribute->isStatic;
# 3.5.9.1. Let Q be ? Get(O, id).
my $id = $attribute->name;
push(@$outputArray, " auto id = Identifier::fromString(vm, reinterpret_cast<const LChar*>(\"${id}\"), strlen(\"${id}\"));\n");
push(@$outputArray, " auto valueToForwardTo = thisObject.get(&lexicalGlobalObject, id);\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, false);\n");
# 3.5.9.2. If Type(Q) is not Object, then throw a TypeError.
push(@$outputArray, " if (UNLIKELY(!valueToForwardTo.isObject())) {\n");
push(@$outputArray, " throwTypeError(&lexicalGlobalObject, throwScope);\n");
push(@$outputArray, " return false;\n");
push(@$outputArray, " }\n");
# 3.5.9.3. Let forwardId be the identifier argument of the [PutForwards] extended attribute.
my $forwardId = $attribute->extendedAttributes->{PutForwards};
push(@$outputArray, " auto forwardId = Identifier::fromString(vm, reinterpret_cast<const LChar*>(\"${forwardId}\"), strlen(\"${forwardId}\"));\n");
# 3.5.9.4. Perform ? Set(Q, forwardId, V, false).
push(@$outputArray, " PutPropertySlot slot(valueToForwardTo, false);\n");
push(@$outputArray, " asObject(valueToForwardTo)->methodTable(vm)->put(asObject(valueToForwardTo), &lexicalGlobalObject, forwardId, value, slot);\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, false);\n");
push(@$outputArray, " return true;\n");
} else {
push(@$outputArray, " auto& impl = thisObject.wrapped();\n") if !$attribute->isStatic;
if ($codeGenerator->IsEnumType($attribute->type)) {
# As per section 3.5.6 of https://heycam.github.io/webidl/#dfn-attribute-setter, enumerations do not use
# the standard conversion, but rather silently fail on invalid enumeration values.
push(@$outputArray, " auto optionalNativeValue = parseEnumeration<" . GetEnumerationClassName($attribute->type, $interface) . ">(lexicalGlobalObject, value);\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, false);\n");
push(@$outputArray, " if (UNLIKELY(!optionalNativeValue))\n");
push(@$outputArray, " return false;\n");
push(@$outputArray, " auto nativeValue = optionalNativeValue.value();\n");
} else {
my $globalObjectReference = $attribute->isStatic ? "*jsCast<JSDOMGlobalObject*>(lexicalGlobalObject)" : "*thisObject.globalObject()";
my $exceptionThrower = GetAttributeExceptionThrower($interface, $attribute);
my $toNativeExpression = JSValueToNative($interface, $attribute, "value", $attribute->extendedAttributes->{Conditional}, "&lexicalGlobalObject", "lexicalGlobalObject", "thisObject", $globalObjectReference, $exceptionThrower);
push(@$outputArray, " auto nativeValue = ${toNativeExpression};\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, false);\n");
}
my ($baseFunctionName, @arguments) = $codeGenerator->SetterExpression(\%implIncludes, $interface->type->name, $attribute);
push(@arguments, PassArgumentExpression("nativeValue", $attribute));
my $functionName = GetFullyQualifiedImplementationCallName($interface, $attribute, $baseFunctionName, "impl", $conditional);
AddAdditionalArgumentsForImplementationCall(\@arguments, $interface, $attribute, "impl", "lexicalGlobalObject", "", "thisObject");
unshift(@arguments, GenerateCallWithUsingReferences($attribute->extendedAttributes->{SetterCallWith}, $outputArray, "false", "thisObject"));
unshift(@arguments, GenerateCallWithUsingReferences($attribute->extendedAttributes->{CallWith}, $outputArray, "false", "thisObject"));
my $callTracer = $attribute->extendedAttributes->{CallTracer} || $interface->extendedAttributes->{CallTracer};
if ($callTracer) {
my $indent = " ";
my @callTracerArguments = ("nativeValue");
GenerateCallTracer($outputArray, $callTracer, $attribute->name, \@callTracerArguments, $indent);
}
my $functionString = "${functionName}(" . join(", ", @arguments) . ")";
push(@$outputArray, " invokeFunctorPropagatingExceptionIfNecessary(lexicalGlobalObject, throwScope, [&] {\n");
push(@$outputArray, " return $functionString;\n");
push(@$outputArray, " });\n");
push(@$outputArray, " return true;\n");
}
push(@$outputArray, "}\n\n");
}
sub GenerateAttributeSetterTrampolineDefinition
{
my ($outputArray, $interface, $className, $attribute, $attributeSetterName, $attributeSetterBodyName, $conditional) = @_;
AddToImplIncludes("JSDOMAttribute.h", $conditional);
my $callAttributeSetterName = "set";
$callAttributeSetterName .= "Static" if $attribute->isStatic;
$callAttributeSetterName .= "PassingPropertyName" if AttributeSetterNeedsPropertyName($attribute);
my @templateParameters = ();
push(@templateParameters, $attributeSetterBodyName);
push(@templateParameters, "CastedThisErrorBehavior::ReturnEarly") if $attribute->extendedAttributes->{LegacyLenientThis};
push(@$outputArray, "JSC_DEFINE_CUSTOM_SETTER(${attributeSetterName}, (JSGlobalObject* lexicalGlobalObject, EncodedJSValue thisValue, EncodedJSValue encodedValue, PropertyName attributeName))\n");
push(@$outputArray, "{\n");
push(@$outputArray, " return IDLAttribute<${className}>::${callAttributeSetterName}<" . join(", ", @templateParameters) . ">(*lexicalGlobalObject, thisValue, encodedValue, attributeName);\n");
push(@$outputArray, "}\n\n");
}
sub GenerateAttributeSetterDefinition
{
my ($outputArray, $interface, $className, $attribute) = @_;
return if IsReadonly($attribute);
return if $codeGenerator->IsConstructorType($attribute->type);
return if IsJSBuiltin($interface, $attribute);
return if $attribute->extendedAttributes->{DelegateToSharedSyntheticAttribute};
my $conditional = $attribute->extendedAttributes->{Conditional};
if ($conditional) {
my $conditionalString = $codeGenerator->GenerateConditionalStringFromAttributeValue($conditional);
push(@$outputArray, "#if ${conditionalString}\n");;
}
my $readWriteConditional = $attribute->extendedAttributes->{ConditionallyReadWrite};
if ($readWriteConditional) {
my $conditionalString = $codeGenerator->GenerateConditionalStringFromAttributeValue($readWriteConditional);
push(@$outputArray, "#if ${conditionalString}\n");;
}
my $attributeSetterName = GetAttributeSetterName($interface, $className, $attribute);
my $attributeSetterBodyName = $attributeSetterName . "Setter";
GenerateAttributeSetterBodyDefinition($outputArray, $interface, $className, $attribute, $attributeSetterBodyName, $conditional);
GenerateAttributeSetterTrampolineDefinition($outputArray, $interface, $className, $attribute, $attributeSetterName, $attributeSetterBodyName, $conditional);
push(@$outputArray, "#endif\n\n") if $readWriteConditional;
push(@$outputArray, "#endif\n\n") if $conditional;
}
sub GenerateOperationTrampolineDefinition
{
my ($outputArray, $interface, $className, $operation, $functionName, $functionBodyName) = @_;
my $hasPromiseReturnType = $codeGenerator->IsPromiseType($operation->type);
my $idlOperationType = $hasPromiseReturnType ? "IDLOperationReturningPromise" : "IDLOperation";
my $callFunctionName = "call";
$callFunctionName .= "Static" if $operation->isStatic;
$callFunctionName .= "ReturningOwnPromise" if $hasPromiseReturnType && $operation->extendedAttributes->{ReturnsOwnPromise};
my @callFunctionTemplateArguments = ();
push(@callFunctionTemplateArguments, $functionBodyName);
push(@callFunctionTemplateArguments, "CastedThisErrorBehavior::Assert") if ($operation->extendedAttributes->{PrivateIdentifier} and not $operation->extendedAttributes->{PublicIdentifier});
push(@$outputArray, "JSC_DEFINE_HOST_FUNCTION(${functionName}, (JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame))\n");
push(@$outputArray, "{\n");
push(@$outputArray, " return ${idlOperationType}<${className}>::${callFunctionName}<" . join(", ", @callFunctionTemplateArguments) . ">(*lexicalGlobalObject, *callFrame, \"" . $operation->name . "\");\n");
push(@$outputArray, "}\n\n");
}
sub GenerateOperationBodyDefinition
{
my ($outputArray, $interface, $className, $operation, $functionName, $functionImplementationName, $functionBodyName, $isOverloaded, $generatingOverloadDispatcher) = @_;
my $hasPromiseReturnType = $codeGenerator->IsPromiseType($operation->type);
my $idlOperationType = $hasPromiseReturnType ? "IDLOperationReturningPromise" : "IDLOperation";
my $conditional = $operation->extendedAttributes->{Conditional};
my @signatureArguments = ();
push(@signatureArguments, "JSC::JSGlobalObject* lexicalGlobalObject");
push(@signatureArguments, "JSC::CallFrame* callFrame");
push(@signatureArguments, "typename ${idlOperationType}<${className}>::ClassParameter castedThis") if !$operation->isStatic;
push(@signatureArguments, "Ref<DeferredPromise>&& promise") if $hasPromiseReturnType && !$operation->extendedAttributes->{ReturnsOwnPromise};
push(@$outputArray, "static inline JSC::EncodedJSValue ${functionBodyName}(" . join(", ", @signatureArguments) . ")\n");
push(@$outputArray, "{\n");
push(@$outputArray, " auto& vm = JSC::getVM(lexicalGlobalObject);\n");
push(@$outputArray, " auto throwScope = DECLARE_THROW_SCOPE(vm);\n");
push(@$outputArray, " UNUSED_PARAM(throwScope);\n");
push(@$outputArray, " UNUSED_PARAM(callFrame);\n");
GenerateCustomElementReactionsStackIfNeeded($outputArray, $operation, "*lexicalGlobalObject") unless $generatingOverloadDispatcher;
# For overloads, we generate the security check in the overload dispatcher, instead of the body of each overload, as per specification:
# https://heycam.github.io/webidl/#dfn-create-operation-function
if (!$isOverloaded || $generatingOverloadDispatcher) {
if ($interface->extendedAttributes->{CheckSecurity} and !$operation->extendedAttributes->{DoNotCheckSecurity}) {
assert("Security checks are not supported for static operations.") if $operation->isStatic;
if ($interface->type->name eq "DOMWindow") {
AddToImplIncludes("JSDOMBindingSecurityInlines.h", $conditional);
push(@$outputArray, " bool shouldAllowAccess = BindingSecurity::shouldAllowAccessToDOMWindow(lexicalGlobalObject, *castedThis, ThrowSecurityError);\n");
} else {
AddToImplIncludes("JSDOMBindingSecurity.h", $conditional);
push(@$outputArray, " bool shouldAllowAccess = BindingSecurity::shouldAllowAccessToDOMWindow(lexicalGlobalObject, castedThis->wrapped().window(), ThrowSecurityError);\n");
}
push(@$outputArray, " EXCEPTION_ASSERT_UNUSED(throwScope, !throwScope.exception() || !shouldAllowAccess);\n");
push(@$outputArray, " if (!shouldAllowAccess)\n");
push(@$outputArray, " return JSValue::encode(jsUndefined());\n");
}
}
my $indent = " ";
if ($generatingOverloadDispatcher) {
my @argumentsToForward = ();
push(@argumentsToForward, "lexicalGlobalObject");
push(@argumentsToForward, "callFrame");
push(@argumentsToForward, "castedThis") if !$operation->isStatic;
push(@argumentsToForward, "WTFMove(promise)") if $hasPromiseReturnType && !$operation->extendedAttributes->{ReturnsOwnPromise};
GenerateOverloadDispatcher($operation, $interface, $functionName, "Body", join(", ", @argumentsToForward));
} elsif (HasCustomMethod($operation)) {
GenerateImplementationCustomFunctionCall($outputArray, $operation, $interface, $className, $functionImplementationName, $indent);
} else {
if (!$operation->extendedAttributes->{ForwardToMapLike} && !$operation->extendedAttributes->{ForwardToSetLike} && !$operation->isStatic) {
push(@$outputArray, " auto& impl = castedThis->wrapped();\n");
}
GenerateArgumentsCountCheck($outputArray, $operation, $interface, $indent);
my $functionString = GenerateParametersCheck($outputArray, $operation, $interface, $functionImplementationName, $indent);
my $hasThrowScope = 1;
if ($operation->extendedAttributes->{ResultField}) {
my $resultName = $operation->extendedAttributes->{ResultField};
push(@$outputArray, " auto implResult = $functionString;\n");
GenerateImplementationFunctionCall($outputArray, $operation, $interface, "WTFMove(implResult.$resultName)", $indent, $hasThrowScope);
} else {
GenerateImplementationFunctionCall($outputArray, $operation, $interface, $functionString, $indent, $hasThrowScope);
}
}
push(@$outputArray, "}\n\n");
}
sub GenerateOperationDefinition
{
my ($outputArray, $interface, $className, $operation) = @_;
return if IsJSBuiltin($interface, $operation);
return if $operation->extendedAttributes->{FromIterable};
if ($operation->extendedAttributes->{Default}) {
return GenerateDefaultOperationDefinition($outputArray, $interface, $className, $operation);
}
my $isCustom = HasCustomMethod($operation);
my $isOverloaded = $operation->{overloads} && @{$operation->{overloads}} > 1;
assert("[Custom] is not supported for overloaded operations.") if $isCustom && $isOverloaded;
my $inAppleCopyright = 0;
if ($operation->extendedAttributes->{AppleCopyright}) {
if (!$inAppleCopyright) {
push(@$outputArray, $beginAppleCopyrightForSourceFiles);
$inAppleCopyright = 1;
}
} elsif ($inAppleCopyright) {
push(@$outputArray, $endAppleCopyright);
$inAppleCopyright = 0;
}
my $conditional = $operation->extendedAttributes->{Conditional};
if ($conditional) {
my $conditionalString = $codeGenerator->GenerateConditionalStringFromAttributeValue($conditional);
push(@$outputArray, "#if ${conditionalString}\n");
}
my $hasPromiseReturnType = $codeGenerator->IsPromiseType($operation->type);
AddToImplIncludesForIDLType($operation->type, $conditional) unless $isCustom or $hasPromiseReturnType;
AddToImplIncludes("JSDOMOperation.h", $conditional) if !$hasPromiseReturnType;
AddToImplIncludes("JSDOMOperationReturningPromise.h", $conditional) if $hasPromiseReturnType;
my $functionName = GetFunctionName($interface, $className, $operation);
my $functionImplementationName = $operation->extendedAttributes->{ImplementedAs} || $codeGenerator->WK_lcfirst($operation->name);
my $functionBodyName = ($isOverloaded ? $functionName . $operation->{overloadIndex} : $functionName) . "Body";
GenerateOperationBodyDefinition($outputArray, $interface, $className, $operation, $functionName, $functionImplementationName, $functionBodyName, $isOverloaded);
# Overloaded operations don't generate a trampoline for each overload, and instead have a single dispatch trampoline
# that gets generated after the last overload body has been generated.
unless ($isOverloaded) {
GenerateOperationTrampolineDefinition($outputArray, $interface, $className, $operation, $functionName, $functionBodyName);
}
push(@$outputArray, "#endif\n\n") if $conditional;
# Generate a function dispatching call to the rest of the overloads.
if ($isOverloaded && $operation->{overloadIndex} == @{$operation->{overloads}}) {
my $overloadsConditionalAttribute = GetConditionalForOperationConsideringOverloads($operation);
my $overloadsConditionalString = $overloadsConditionalAttribute ? $codeGenerator->GenerateConditionalStringFromAttributeValue($overloadsConditionalAttribute) : undef;
push(@$outputArray, "#if ${overloadsConditionalString}\n\n") if $overloadsConditionalString;
my $overloadDispatcherFunctionBodyName = $functionName . "OverloadDispatcher";
GenerateOperationBodyDefinition($outputArray, $interface, $className, $operation, $functionName, $functionImplementationName, $overloadDispatcherFunctionBodyName, $isOverloaded, 1);
GenerateOperationTrampolineDefinition($outputArray, $interface, $className, $operation, $functionName, $overloadDispatcherFunctionBodyName);
push(@$outputArray, "#endif\n\n") if $overloadsConditionalString;
}
if ($operation->extendedAttributes->{DOMJIT}) {
if ($conditional) {
my $conditionalString = $codeGenerator->GenerateConditionalStringFromAttributeValue($conditional);
push(@$outputArray, "#if ${conditionalString}\n");
}
AddToImplIncludes("<JavaScriptCore/FrameTracers.h>", $conditional);
my $nameOfFunctionWithoutTypeCheck = $codeGenerator->WK_lcfirst($functionName) . "WithoutTypeCheck";
push(@$outputArray, "JSC_DEFINE_JIT_OPERATION(${nameOfFunctionWithoutTypeCheck}, JSC::EncodedJSValue, (JSC::JSGlobalObject* lexicalGlobalObject, $className* castedThis");
foreach my $argument (@{$operation->arguments}) {
my $type = $argument->type;
my $argumentType = GetArgumentTypeForFunctionWithoutTypeCheck($interface, $type);
my $name = $argument->name;
my $encodedName = "encoded" . $codeGenerator->WK_ucfirst($name);
push(@$outputArray, ", ${argumentType} ${encodedName}");
}
push(@$outputArray, "))\n");
push(@$outputArray, "{\n");
push(@$outputArray, " UNUSED_PARAM(lexicalGlobalObject);\n");
push(@$outputArray, " VM& vm = JSC::getVM(lexicalGlobalObject);\n");
push(@$outputArray, " IGNORE_WARNINGS_BEGIN(\"frame-address\")\n");
push(@$outputArray, " CallFrame* callFrame = DECLARE_CALL_FRAME(vm);\n");
push(@$outputArray, " IGNORE_WARNINGS_END\n");
push(@$outputArray, " JSC::JITOperationPrologueCallFrameTracer tracer(vm, callFrame);\n");
push(@$outputArray, " auto throwScope = DECLARE_THROW_SCOPE(vm);\n");
push(@$outputArray, " UNUSED_PARAM(throwScope);\n");
push(@$outputArray, " auto& impl = castedThis->wrapped();\n");
my $implFunctionName = GetFullyQualifiedImplementationCallName($interface, $operation, $functionImplementationName, "impl", $conditional);
my @arguments = ();
AddAdditionalArgumentsForImplementationCall(\@arguments, $interface, $operation, "impl", "*lexicalGlobalObject", "*callFrame", "*castedThis");
foreach my $argument (@{$operation->arguments}) {
my $value = "";
my $type = $argument->type;
my $name = $argument->name;
my $encodedName = "encoded" . $codeGenerator->WK_ucfirst($name);
my $shouldPassByReference = ShouldPassArgumentByReference($argument);
my ($nativeValue, $mayThrowException) = ToNativeForFunctionWithoutTypeCheck($interface, $argument, $encodedName, $operation->extendedAttributes->{Conditional});
push(@$outputArray, " auto $name = ${nativeValue};\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, encodedJSValue());\n") if $mayThrowException;
$value = "WTFMove($name)";
if ($shouldPassByReference) {
$value = "*$name";
}
push(@arguments, $value);
}
my $functionString = "$implFunctionName(" . join(", ", @arguments) . ")";
push(@$outputArray, " return JSValue::encode(" . NativeToJSValueUsingPointers($operation, $interface, $functionString, "*castedThis->globalObject()") . ");\n");
push(@$outputArray, "}\n\n");
push(@$outputArray, "#endif\n\n") if $conditional;
}
push(@$outputArray, $endAppleCopyright) if $inAppleCopyright;
}
sub GenerateDefaultOperationDefinition
{
my ($outputArray, $interface, $className, $operation) = @_;
if ($operation->name eq "toJSON" && $operation->type->name eq "object") {
return GenerateDefaultToJSONOperationDefinition($outputArray, $interface, $className, $operation);
}
die "[Default] is not support for for this operation (" . $operation->name . "). It is only currently defined for 'object toJSON();'."
}
sub GenerateDefaultToJSONOperationDefinition
{
my ($outputArray, $interface, $className, $operation) = @_;
# https://heycam.github.io/webidl/#es-default-tojson
my @inheritenceStack = ();
push(@inheritenceStack, $interface);
$codeGenerator->ForAllParents($interface, sub {
my $parentInterface = shift;
push(@inheritenceStack, $parentInterface);
}, 0);
my $functionName = GetFunctionName($interface, $className, $operation);
push(@$outputArray, "static inline EncodedJSValue ${functionName}Body(JSGlobalObject* lexicalGlobalObject, CallFrame*, ${className}* castedThis)\n");
push(@$outputArray, "{\n");
push(@implContent, " auto& vm = JSC::getVM(lexicalGlobalObject);\n");
push(@implContent, " auto throwScope = DECLARE_THROW_SCOPE(vm);\n");
push(@implContent, " UNUSED_PARAM(throwScope);\n");
push(@implContent, " auto& impl = castedThis->wrapped();\n");
AddToImplIncludes("<JavaScriptCore/ObjectConstructor.h>");
push(@implContent, " auto* result = constructEmptyObject(lexicalGlobalObject, castedThis->globalObject()->objectPrototype());\n");
while (@inheritenceStack) {
my $currentInterface = pop(@inheritenceStack);
my $hasDefaultToJSONOperation = 0;
foreach my $operation (@{$currentInterface->operations}) {
if ($operation->extendedAttributes->{Default} && $operation->name eq "toJSON" && $operation->type->name eq "object") {
$hasDefaultToJSONOperation = 1;
last;
}
}
if ($hasDefaultToJSONOperation) {
foreach my $attribute (@{$currentInterface->attributes}) {
next if $attribute->isStatic;
next if !$codeGenerator->IsJSONType($interface, $attribute->type);
assert("[CachedAttribute] is not currently support on interfaces with [Default] object toJSON();") if $attribute->extendedAttributes->{CachedAttribute};
my $conditional = $attribute->extendedAttributes->{Conditional};
if ($conditional) {
my $conditionalString = $codeGenerator->GenerateConditionalStringFromAttributeValue($conditional);
push(@$outputArray, "#if ${conditionalString}\n");;
}
my $attributeName = $attribute->name;
my $toJSExpression = "";
my $mayThrowException = 0;
if (HasCustomGetter($attribute)) {
my $implGetterFunctionName = $codeGenerator->WK_lcfirst($attribute->extendedAttributes->{ImplementedAs} || $attributeName);
$toJSExpression = "castedThis->${implGetterFunctionName}(*lexicalGlobalObject)";
} else {
my ($baseFunctionName, @arguments) = $codeGenerator->GetterExpression(\%implIncludes, $currentInterface->type->name, $attribute);
my $functionName = GetFullyQualifiedImplementationCallName($currentInterface, $attribute, $baseFunctionName, "impl", $conditional);
$mayThrowException = NativeToJSValueMayThrow($attribute);
$toJSExpression = NativeToJSValue($attribute, $currentInterface, "${functionName}(" . join(", ", @arguments) . ")", "*lexicalGlobalObject", "*castedThis->globalObject()");
}
my $needsRuntimeCheck = NeedsRuntimeCheck($currentInterface, $attribute);
if ($needsRuntimeCheck) {
my $runtimeEnableConditionalString = GenerateRuntimeEnableConditionalString($currentInterface, $attribute, "castedThis->globalObject()");
push(@$outputArray, " if (${runtimeEnableConditionalString}) {\n");
push(@$outputArray, " auto ${attributeName}Value = ${toJSExpression};\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, { });\n") if $mayThrowException;
push(@$outputArray, " result->putDirect(vm, Identifier::fromString(vm, \"${attributeName}\"), ${attributeName}Value);\n");
push(@$outputArray, " }\n");
} else {
push(@$outputArray, " auto ${attributeName}Value = ${toJSExpression};\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, { });\n") if $mayThrowException;
push(@$outputArray, " result->putDirect(vm, Identifier::fromString(vm, \"${attributeName}\"), ${attributeName}Value);\n");
}
if ($conditional) {
push(@$outputArray, "#endif\n\n") ;
}
}
}
}
push(@$outputArray, " return JSValue::encode(result);\n");
push(@$outputArray, "}\n");
push(@$outputArray, "\n");
my $interfaceName = $interface->type->name;
push(@$outputArray, "JSC_DEFINE_HOST_FUNCTION(${functionName}, (JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame))\n");
push(@$outputArray, "{\n");
push(@$outputArray, " return IDLOperation<JS${interfaceName}>::call<${functionName}Body>(*lexicalGlobalObject, *callFrame, \"toJSON\");\n");
push(@$outputArray, "}\n");
push(@$outputArray, "\n");
}
sub GenerateGetCallData
{
my ($outputArray, $interface, $className) = @_;
return if $interface->extendedAttributes->{CustomGetCallData};
AddToImplIncludes("JSPluginElementFunctions.h");
push(@$outputArray, "CallData ${className}::getCallData(JSCell* cell)\n");
push(@$outputArray, "{\n");
push(@$outputArray, " auto* thisObject = jsCast<${className}*>(cell);\n");
push(@$outputArray, " ASSERT_GC_OBJECT_INHERITS(thisObject, info());\n\n");
push(@$outputArray, " return pluginElementCustomGetCallData(thisObject);\n");
push(@$outputArray, "}\n");
push(@$outputArray, "\n");
}
sub GenerateCallWithUsingReferences
{
my ($callWith, $outputArray, $returnValue, $thisReference, $callFrameReference, $indent) = @_;
my $callFramePointer = $callFrameReference && "&$callFrameReference";
my $globalObject = "jsCast<JSDOMGlobalObject*>(&lexicalGlobalObject)";
return GenerateCallWith($callWith, $outputArray, $returnValue, $returnValue, $callFramePointer, $callFrameReference, $globalObject, $globalObject, $thisReference, $indent);
}
# FIXME: We should remove GenerateCallWithUsingPointers and combine GenerateCallWithUsingReferences and GenerateCallWith
sub GenerateCallWithUsingPointers
{
my ($callWith, $outputArray, $returnValue, $thisReference, $indent) = @_;
my $callFramePointer = "callFrame";
my $callFrameReference = "*callFrame";
my $globalObject = "jsCast<JSDOMGlobalObject*>(lexicalGlobalObject)";
return GenerateCallWith($callWith, $outputArray, $returnValue, $returnValue, $callFramePointer, $callFrameReference, $globalObject, $globalObject, $thisReference, $indent);
}
sub GenerateConstructorCallWithUsingPointers
{
my ($callWith, $outputArray, $visibleInterfaceName, $thisReference, $indent) = @_;
my $callFramePointer = "callFrame";
my $callFrameReference = "*callFrame";
my $globalObject = "castedThis->globalObject()";
my $contextMissing = "throwConstructorScriptExecutionContextUnavailableError(*lexicalGlobalObject, throwScope, \"${visibleInterfaceName}\")";
my $scriptExecutionContextAccessor = "castedThis";
return GenerateCallWith($callWith, $outputArray, "", $contextMissing, $callFramePointer, $callFrameReference, $globalObject, $scriptExecutionContextAccessor, $thisReference, $indent);
}
sub GenerateCallWith
{
my ($callWith, $outputArray, $returnValue, $contextMissing, $callFramePointer, $callFrameReference, $globalObject, $scriptExecutionContextAccessor, $thisReference, $indent) = @_;
return () unless $callWith;
$indent ||= " ";
my @callWithArgs;
if ($codeGenerator->ExtendedAttributeContains($callWith, "ExecState")) {
push(@callWithArgs, "*${globalObject}");
push(@callWithArgs, $callFrameReference);
}
if ($codeGenerator->ExtendedAttributeContains($callWith, "GlobalObject")) {
push(@callWithArgs, "*${globalObject}");
}
if ($codeGenerator->ExtendedAttributeContains($callWith, "ScriptExecutionContext")) {
push(@$outputArray, $indent . "auto* context = ${scriptExecutionContextAccessor}->scriptExecutionContext();\n");
push(@$outputArray, $indent . "if (UNLIKELY(!context))\n");
push(@$outputArray, $indent . " return" . ($contextMissing ? " " . $contextMissing : "") . ";\n");
push(@callWithArgs, "*context");
}
if ($codeGenerator->ExtendedAttributeContains($callWith, "Document")) {
AddToImplIncludes("Document.h");
push(@$outputArray, $indent . "auto* context = ${scriptExecutionContextAccessor}->scriptExecutionContext();\n");
push(@$outputArray, $indent . "if (UNLIKELY(!context))\n");
push(@$outputArray, $indent . " return" . ($contextMissing ? " " . $contextMissing : "") . ";\n");
push(@$outputArray, $indent . "ASSERT(context->isDocument());\n");
push(@$outputArray, $indent . "auto& document = downcast<Document>(*context);\n");
push(@callWithArgs, "document");
}
if ($codeGenerator->ExtendedAttributeContains($callWith, "IncumbentDocument")) {
AddToImplIncludes("DOMWindow.h");
AddToImplIncludes("JSDOMWindowBase.h");
push(@$outputArray, $indent . "auto* incumbentDocument = incumbentDOMWindow(*$globalObject, $callFrameReference).document();\n");
push(@$outputArray, $indent . "if (!incumbentDocument)\n");
push(@$outputArray, $indent . " return" . ($returnValue ? " " . $returnValue : "") . ";\n");
push(@callWithArgs, "*incumbentDocument");
}
if ($codeGenerator->ExtendedAttributeContains($callWith, "ResponsibleDocument")) {
AddToImplIncludes("DOMWindow.h");
AddToImplIncludes("JSDOMWindowBase.h");
push(@callWithArgs, "responsibleDocument(${globalObject}->vm(), $callFrameReference)");
}
if ($codeGenerator->ExtendedAttributeContains($callWith, "ActiveWindow")) {
AddToImplIncludes("DOMWindow.h");
AddToImplIncludes("JSDOMWindowBase.h");
push(@callWithArgs, "activeDOMWindow(*$globalObject)");
}
if ($codeGenerator->ExtendedAttributeContains($callWith, "IncumbentWindow")) {
AddToImplIncludes("DOMWindow.h");
AddToImplIncludes("JSDOMWindowBase.h");
push(@callWithArgs, "incumbentDOMWindow(*$globalObject" . ($callFrameReference ? ", " . $callFrameReference : "") . ")");
}
if ($codeGenerator->ExtendedAttributeContains($callWith, "FirstWindow")) {
AddToImplIncludes("DOMWindow.h");
AddToImplIncludes("JSDOMWindowBase.h");
push(@callWithArgs, "firstDOMWindow(*$globalObject)");
}
if ($codeGenerator->ExtendedAttributeContains($callWith, "RuntimeFlags")) {
push(@callWithArgs, "${globalObject}->runtimeFlags()");
}
if ($codeGenerator->ExtendedAttributeContains($callWith, "World")) {
push(@callWithArgs, "worldForDOMObject(${thisReference})");
}
if ($codeGenerator->ExtendedAttributeContains($callWith, "PropertyName")) {
AddToImplIncludes("JSDOMConvertStrings.h");
push(@callWithArgs, "propertyNameToAtomString(propertyName)");
}
return @callWithArgs;
}
sub GenerateArgumentsCountCheck
{
my ($outputArray, $operation, $interface, $indent) = @_;
# Overloaded operations don't need to check the argument count since the
# dispatch function does for them.
return if $operation->{overloads} && @{$operation->{overloads}} > 1;
my $numMandatoryArguments = @{$operation->arguments};
foreach my $argument (reverse(@{$operation->arguments})) {
if ($argument->isOptional or $argument->isVariadic) {
$numMandatoryArguments--;
} else {
last;
}
}
if ($numMandatoryArguments >= 1) {
push(@$outputArray, $indent . "if (UNLIKELY(callFrame->argumentCount() < $numMandatoryArguments))\n");
push(@$outputArray, $indent . " return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));\n");
}
}
my %automaticallyGeneratedDefaultValues = (
"any" => "undefined",
# toString() will convert undefined to the string "undefined";
# (note that this optimizes a behavior that is almost never useful)
"DOMString" => "\"undefined\"",
"USVString" => "\"undefined\"",
# JSValue::toBoolean() will convert undefined to false.
"boolean" => "false",
# JSValue::toInt*() / JSValue::toUint*() will convert undefined to 0.
"byte" => "0",
"long long" => "0",
"long" => "0",
"octet" => "0",
"short" => "0",
"unsigned long long" => "0",
"unsigned long" => "0",
"unsigned short" => "0",
# toNumber() / toFloat() convert undefined to NaN.
"double" => "NaN",
"float" => "NaN",
"unrestricted double" => "NaN",
"unrestricted float" => "NaN",
);
sub WillConvertUndefinedToDefaultParameterValue
{
my ($parameterType, $defaultValue) = @_;
my $automaticallyGeneratedDefaultValue = $automaticallyGeneratedDefaultValues{$parameterType->name};
return 1 if defined $automaticallyGeneratedDefaultValue && $automaticallyGeneratedDefaultValue eq $defaultValue;
return 1 if $defaultValue eq "null" && $codeGenerator->IsWrapperType($parameterType);
return 1 if $defaultValue eq "[]" && $codeGenerator->IsDictionaryType($parameterType);
return 0;
}
sub GenerateParametersCheck
{
my ($outputArray, $operation, $interface, $functionImplementationName, $indent) = @_;
my $interfaceName = $interface->type->name;
my $visibleInterfaceName = $codeGenerator->GetVisibleInterfaceName($interface);
my $numArguments = @{$operation->arguments};
my $conditional = $operation->extendedAttributes->{Conditional};
my $isConstructor = $operation->isConstructor;
my $functionName = GetFullyQualifiedImplementationCallName($interface, $operation, $functionImplementationName, "impl", $conditional);
my @arguments = ();
AddAdditionalArgumentsForImplementationCall(\@arguments, $interface, $operation, "impl", "*lexicalGlobalObject", "*callFrame", "*castedThis");
my $callWith = $operation->extendedAttributes->{CallWith};
my $quotedFunctionName;
if (!$isConstructor) {
my $name = $operation->name;
$quotedFunctionName = "\"$name\"";
push(@arguments, GenerateCallWithUsingPointers($callWith, \@$outputArray, "JSValue::encode(jsUndefined())", "*castedThis"));
} else {
$quotedFunctionName = "nullptr";
unless ($callWith) {
$callWith = $operation->extendedAttributes->{LegacyFactoryFunctionCallWith};
}
push(@arguments, GenerateConstructorCallWithUsingPointers($callWith, \@$outputArray, $visibleInterfaceName, "*castedThis"));
}
my $argumentIndex = 0;
foreach my $argument (@{$operation->arguments}) {
my $type = $argument->type;
if ($argument->isOptional && !defined($argument->default)) {
# As per Web IDL, optional dictionary arguments are always considered to have a default value of an empty dictionary, unless otherwise specified.
$argument->default("[]") if $codeGenerator->IsDictionaryType($type);
# Treat undefined the same as an empty sequence Or frozen array.
$argument->default("[]") if $codeGenerator->IsSequenceOrFrozenArrayType($type);
# We use undefined as default value for optional arguments of type 'any' unless specified otherwise.
$argument->default("undefined") if $type->name eq "any";
# We use the null string as default value for arguments of type DOMString unless specified otherwise.
$argument->default("null") if $codeGenerator->IsStringType($type);
# As per Web IDL, passing undefined for a nullable argument is treated as null. Therefore, use null as
# default value for nullable arguments unless otherwise specified.
$argument->default("null") if $type->isNullable;
}
my $name = $argument->name;
my $value = $name;
if ($argument->isVariadic) {
AddToImplIncludes("JSDOMConvertVariadic.h", $conditional);
AddToImplIncludesForIDLType($type, $conditional);
my $IDLType = GetIDLType($interface, $type);
push(@$outputArray, $indent . "auto ${name} = convertVariadicArguments<${IDLType}>(*lexicalGlobalObject, *callFrame, ${argumentIndex});\n");
push(@$outputArray, $indent . "RETURN_IF_EXCEPTION(throwScope, encodedJSValue());\n");
$value = "WTFMove(${name})";
} else {
my $argumentLookupForConversion;
my $optionalCheck;
my $nativeValueCastFunction;
if ($argument->isOptional) {
assert("[ReturnValue] is not supported for optional arguments") if $argument->extendedAttributes->{ReturnValue};
if (defined($argument->default)) {
push(@$outputArray, $indent . "EnsureStillAliveScope argument${argumentIndex} = callFrame->argument($argumentIndex);\n");
if (!WillConvertUndefinedToDefaultParameterValue($type, $argument->default)) {
my $defaultValue = GenerateDefaultValue($interface, $argument, $argument->type, $argument->default);
$optionalCheck = "argument${argumentIndex}.value().isUndefined() ? $defaultValue : ";
}
$argumentLookupForConversion = "argument${argumentIndex}.value()"
} else {
my $argumentIDLType = GetIDLType($interface, $argument->type);
my $defaultValue;
if ($codeGenerator->IsCallbackInterface($type) || $codeGenerator->IsCallbackFunction($type)) {
$defaultValue = "Converter<$argumentIDLType>::ReturnType()";
} elsif ($codeGenerator->IsPromiseType($argument->type) || $codeGenerator->IsWrapperType($type)) {
$defaultValue = "nullptr";
} else {
$defaultValue = "std::optional<Converter<$argumentIDLType>::ReturnType>()";
$nativeValueCastFunction = "std::optional<Converter<$argumentIDLType>::ReturnType>";
}
push(@$outputArray, $indent . "EnsureStillAliveScope argument${argumentIndex} = callFrame->argument($argumentIndex);\n");
$optionalCheck = "argument${argumentIndex}.value().isUndefined() ? $defaultValue : ";
$argumentLookupForConversion = "argument${argumentIndex}.value()";
}
} else {
push(@$outputArray, $indent . "EnsureStillAliveScope argument${argumentIndex} = callFrame->uncheckedArgument($argumentIndex);\n");
$argumentLookupForConversion = "argument${argumentIndex}.value()";
}
my $globalObjectReference = $operation->isStatic ? "*jsCast<JSDOMGlobalObject*>(lexicalGlobalObject)" : "*castedThis->globalObject()";
my $argumentExceptionThrower = GetArgumentExceptionThrower($interface, $argument, $argumentIndex, $quotedFunctionName);
my $nativeValue = JSValueToNative($interface, $argument, $argumentLookupForConversion, $conditional, "lexicalGlobalObject", "*lexicalGlobalObject", "*castedThis", $globalObjectReference, $argumentExceptionThrower);
$nativeValue = "${nativeValueCastFunction}(" . $nativeValue . ")" if defined $nativeValueCastFunction;
$nativeValue = $optionalCheck . $nativeValue if defined $optionalCheck;
push(@$outputArray, $indent . "auto $name = ${nativeValue};\n");
push(@$outputArray, $indent . "RETURN_IF_EXCEPTION(throwScope, encodedJSValue());\n");
$value = PassArgumentExpression($name, $argument);
}
push(@arguments, $value);
$argumentIndex++;
}
push(@arguments, "WTFMove(promise)") if $operation->type && $codeGenerator->IsPromiseType($operation->type) && !$operation->extendedAttributes->{PromiseProxy};
return "$functionName(" . join(", ", @arguments) . ")";
}
sub GenerateDictionaryHeader
{
my ($object, $dictionary, $className, $enumerations, $otherDictionaries) = @_;
# - Add default header template and header protection.
push(@headerContentHeader, GenerateHeaderContentHeader($dictionary));
$headerIncludes{"${className}.h"} = 1;
push(@headerContent, "\nnamespace WebCore {\n\n");
push(@headerContent, GenerateDictionaryHeaderContent($dictionary, $className));
push(@headerContent, GenerateEnumerationsHeaderContent($dictionary, $enumerations));
push(@headerContent, GenerateDictionariesHeaderContent($dictionary, $otherDictionaries)) if $otherDictionaries;
push(@headerContent, "} // namespace WebCore\n");
my $conditionalString = $codeGenerator->GenerateConditionalString($dictionary);
push(@headerContent, "\n#endif // ${conditionalString}\n") if $conditionalString;
# - Generate dependencies.
if ($writeDependencies) {
my @ancestors;
my $parentType = $dictionary->parentType;
while (defined($parentType)) {
push(@ancestors, $parentType->name) if $codeGenerator->IsExternalDictionaryType($parentType);
my $parentDictionary = $codeGenerator->GetDictionaryByType($parentType);
assert("Unable to find definition for dictionary named '" . $parentType->name . "'!") unless $parentDictionary;
$parentType = $parentDictionary->parentType;
}
push(@depsContent, "$className.h : ", join(" ", map { "$_.idl" } @ancestors), "\n");
push(@depsContent, map { "$_.idl :\n" } @ancestors);
}
}
sub GenerateDictionaryImplementation
{
my ($object, $dictionary, $className, $enumerations, $otherDictionaries) = @_;
# - Add default header template
push(@implContentHeader, GenerateImplementationContentHeader($dictionary));
push(@implContent, "\n\nnamespace WebCore {\n");
push(@implContent, "using namespace JSC;\n\n");
push(@implContent, GenerateDictionaryImplementationContent($dictionary, $className));
push(@implContent, GenerateEnumerationsImplementationContent($dictionary, $enumerations));
push(@implContent, GenerateDictionariesImplementationContent($dictionary, $otherDictionaries)) if $otherDictionaries;
push(@implContent, "} // namespace WebCore\n");
my $conditionalString = $codeGenerator->GenerateConditionalString($dictionary);
push(@implContent, "\n#endif // ${conditionalString}\n") if $conditionalString;
}
sub GenerateCallbackFunctionHeader
{
my ($object, $callbackFunction, $enumerations, $dictionaries) = @_;
push(@headerContentHeader, GenerateHeaderContentHeader($callbackFunction));
push(@headerContent, "\nnamespace WebCore {\n\n");
my @operations = ();
push(@operations, $callbackFunction->operation);
my @constants = ();
$object->GenerateCallbackHeaderContent($callbackFunction, \@operations, \@constants, \@headerContent, \%headerIncludes);
push(@headerContent, GenerateEnumerationsHeaderContent($callbackFunction, $enumerations));
push(@headerContent, GenerateDictionariesHeaderContent($callbackFunction, $dictionaries));
push(@headerContent, "} // namespace WebCore\n");
my $conditionalString = $codeGenerator->GenerateConditionalString($callbackFunction);
push(@headerContent, "\n#endif // ${conditionalString}\n") if $conditionalString;
}
sub GenerateCallbackFunctionImplementation
{
my ($object, $callbackFunction, $enumerations, $dictionaries) = @_;
push(@implContentHeader, GenerateImplementationContentHeader($callbackFunction));
push(@implContent, "\n\nnamespace WebCore {\n");
push(@implContent, "using namespace JSC;\n\n");
push(@implContent, GenerateEnumerationsImplementationContent($callbackFunction, $enumerations));
push(@implContent, GenerateDictionariesImplementationContent($callbackFunction, $dictionaries));
my @operations = ();
push(@operations, $callbackFunction->operation);
my @constants = ();
$object->GenerateCallbackImplementationContent($callbackFunction, \@operations, \@constants, \@implContent, \%implIncludes);
push(@implContent, "} // namespace WebCore\n");
my $conditionalString = $codeGenerator->GenerateConditionalString($callbackFunction);
push(@implContent, "\n#endif // ${conditionalString}\n") if $conditionalString;
}
sub GenerateCallbackInterfaceHeader
{
my ($object, $callbackInterface, $enumerations, $dictionaries) = @_;
push(@headerContentHeader, GenerateHeaderContentHeader($callbackInterface));
push(@headerContent, "\nnamespace WebCore {\n\n");
$object->GenerateCallbackHeaderContent($callbackInterface, $callbackInterface->operations, $callbackInterface->constants, \@headerContent, \%headerIncludes);
push(@headerContent, GenerateEnumerationsHeaderContent($callbackInterface, $enumerations));
push(@headerContent, GenerateDictionariesHeaderContent($callbackInterface, $dictionaries));
push(@headerContent, "} // namespace WebCore\n");
my $conditionalString = $codeGenerator->GenerateConditionalString($callbackInterface);
push(@headerContent, "\n#endif // ${conditionalString}\n") if $conditionalString;
}
sub GenerateCallbackInterfaceImplementation
{
my ($object, $callbackInterface, $enumerations, $dictionaries) = @_;
push(@implContentHeader, GenerateImplementationContentHeader($callbackInterface));
push(@implContent, "\n\nnamespace WebCore {\n");
push(@implContent, "using namespace JSC;\n\n");
push(@implContent, GenerateEnumerationsImplementationContent($callbackInterface, $enumerations));
push(@implContent, GenerateDictionariesImplementationContent($callbackInterface, $dictionaries));
$object->GenerateCallbackImplementationContent($callbackInterface, $callbackInterface->operations, $callbackInterface->constants, \@implContent, \%implIncludes);
push(@implContent, "} // namespace WebCore\n");
my $conditionalString = $codeGenerator->GenerateConditionalString($callbackInterface);
push(@implContent, "\n#endif // ${conditionalString}\n") if $conditionalString;
}
sub GenerateCallbackHeaderContent
{
my ($object, $interfaceOrCallback, $operations, $constants, $contentRef, $includesRef) = @_;
my $name = $interfaceOrCallback->type->name;
my $callbackDataType = $interfaceOrCallback->extendedAttributes->{IsWeakCallback} ? "JSCallbackDataWeak" : "JSCallbackDataStrong";
my $className = "JS${name}";
$includesRef->{"IDLTypes.h"} = 1;
$includesRef->{"JSCallbackData.h"} = 1;
$includesRef->{"<wtf/Forward.h>"} = 1;
$includesRef->{"${name}.h"} = 1;
my $exportMacro = GetExportMacroForJSClass($interfaceOrCallback);
push(@$contentRef, "class $exportMacro$className final : public ${name} {\n");
push(@$contentRef, "public:\n");
# The static create() method.
push(@$contentRef, " static Ref<$className> create(JSC::JSObject* callback, JSDOMGlobalObject* globalObject)\n");
push(@$contentRef, " {\n");
push(@$contentRef, " return adoptRef(*new ${className}(callback, globalObject));\n");
push(@$contentRef, " }\n\n");
push(@$contentRef, " ScriptExecutionContext* scriptExecutionContext() const { return ContextDestructionObserver::scriptExecutionContext(); }\n\n");
push(@$contentRef, " ~$className() final;\n");
push(@$contentRef, " ${callbackDataType}* callbackData() { return m_data; }\n");
push(@$contentRef, " static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*);\n") if @{$constants};
# Operations
my $numOperations = @{$operations};
if ($numOperations > 0) {
push(@$contentRef, "\n // Functions\n");
foreach my $operation (@{$operations}) {
my @arguments = ();
my $callbackThisObject = $operation->extendedAttributes->{CallbackThisObject};
if ($callbackThisObject) {
my $thisObjectType = $codeGenerator->ParseType($callbackThisObject);
my $IDLType = GetIDLType($interfaceOrCallback, $thisObjectType);
push(@arguments, "typename ${IDLType}::ParameterType thisObject");
}
foreach my $argument (@{$operation->arguments}) {
my $IDLType = GetIDLType($interfaceOrCallback, $argument->type);
push(@arguments, "typename ${IDLType}::ParameterType " . $argument->name);
}
my $nativeReturnType = "CallbackResult<typename " . GetIDLType($interfaceOrCallback, $operation->type) . "::ImplementationType>";
# FIXME: Change the default name (used for callback functions) to something other than handleEvent. It makes little sense.
my $functionName = $operation->extendedAttributes->{ImplementedAs} || $operation->name || "handleEvent";
push(@$contentRef, " ${nativeReturnType} ${functionName}(" . join(", ", @arguments) . ") override;\n");
}
}
push(@$contentRef, "\nprivate:\n");
push(@$contentRef, " ${className}(JSC::JSObject*, JSDOMGlobalObject*);\n\n");
if ($interfaceOrCallback->extendedAttributes->{IsWeakCallback}) {
push(@$contentRef, " bool hasCallback() const final { return m_data && m_data->callback(); }\n\n");
}
push(@$contentRef, " void visitJSFunction(JSC::AbstractSlotVisitor&) override;\n\n") if $interfaceOrCallback->extendedAttributes->{IsWeakCallback};
push(@$contentRef, " void visitJSFunction(JSC::SlotVisitor&) override;\n\n") if $interfaceOrCallback->extendedAttributes->{IsWeakCallback};
push(@$contentRef, " ${callbackDataType}* m_data;\n");
push(@$contentRef, "};\n\n");
# toJS().
push(@$contentRef, $exportMacro . "JSC::JSValue toJS(${name}&);\n");
push(@$contentRef, "inline JSC::JSValue toJS(${name}* impl) { return impl ? toJS(*impl) : JSC::jsNull(); }\n\n");
}
sub GenerateCallbackImplementationContent
{
my ($object, $interfaceOrCallback, $operations, $constants, $contentRef, $includesRef) = @_;
my $name = $interfaceOrCallback->type->name;
my $callbackDataType = $interfaceOrCallback->extendedAttributes->{IsWeakCallback} ? "JSCallbackDataWeak" : "JSCallbackDataStrong";
my $visibleName = $codeGenerator->GetVisibleInterfaceName($interfaceOrCallback);
my $className = "JS${name}";
$includesRef->{"ScriptExecutionContext.h"} = 1;
# Constructor
push(@$contentRef, "${className}::${className}(JSObject* callback, JSDOMGlobalObject* globalObject)\n");
push(@$contentRef, " : ${name}(globalObject->scriptExecutionContext())\n");
push(@$contentRef, " , m_data(new ${callbackDataType}(callback, globalObject, this))\n");
push(@$contentRef, "{\n");
push(@$contentRef, "}\n\n");
# Destructor
push(@$contentRef, "${className}::~${className}()\n");
push(@$contentRef, "{\n");
push(@$contentRef, " ScriptExecutionContext* context = scriptExecutionContext();\n");
push(@$contentRef, " // When the context is destroyed, all tasks with a reference to a callback\n");
push(@$contentRef, " // should be deleted. So if the context is 0, we are on the context thread.\n");
push(@$contentRef, " if (!context || context->isContextThread())\n");
push(@$contentRef, " delete m_data;\n");
push(@$contentRef, " else\n");
push(@$contentRef, " context->postTask(DeleteCallbackDataTask(m_data));\n");
push(@$contentRef, "#ifndef NDEBUG\n");
push(@$contentRef, " m_data = nullptr;\n");
push(@$contentRef, "#endif\n");
push(@$contentRef, "}\n\n");
# Constants.
my $numConstants = @{$constants};
if ($numConstants > 0) {
GenerateConstructorDeclaration($contentRef, $className, $interfaceOrCallback, $name);
my $hashSize = 0;
my $hashName = $className . "ConstructorTable";
my @hashKeys = ();
my @hashValue1 = ();
my @hashValue2 = ();
my @hashSpecials = ();
my %conditionals = ();
my %readWriteConditionals = ();
foreach my $constant (@{$constants}) {
my $name = $constant->name;
push(@hashKeys, $name);
push(@hashValue1, $constant->value);
push(@hashValue2, "0");
push(@hashSpecials, "JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger");
my $implementedBy = $constant->extendedAttributes->{ImplementedBy};
$implIncludes{"${implementedBy}.h"} = 1 if $implementedBy;
my $conditional = $constant->extendedAttributes->{Conditional};
$conditionals{$name} = $conditional if $conditional;
$hashSize++;
}
$object->GenerateHashTable($className, $hashName, $hashSize, \@hashKeys, \@hashSpecials, \@hashValue1, \@hashValue2, \%conditionals, \%readWriteConditionals, 1) if $hashSize > 0;
push(@$contentRef, $codeGenerator->GenerateCompileTimeCheckForEnumsIfNeeded($interfaceOrCallback));
GenerateConstructorDefinitions($contentRef, $className, "", $visibleName, $interfaceOrCallback);
AddToImplIncludes("JSDOMGlobalObjectInlines.h");
push(@$contentRef, "JSValue ${className}::getConstructor(VM& vm, const JSGlobalObject* globalObject)\n");
push(@$contentRef, "{\n");
push(@$contentRef, " return getDOMConstructor<${className}DOMConstructor, DOMConstructorID::${name}>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject));\n");
push(@$contentRef, "}\n\n");
}
# Operations
my $numOperations = @{$operations};
if ($numOperations > 0) {
foreach my $operation (@{$operations}) {
next if $operation->extendedAttributes->{Custom};
AddToIncludesForIDLType($operation->type, $includesRef);
my $nativeReturnType = "CallbackResult<typename " . GetIDLType($interfaceOrCallback, $operation->type) . "::ImplementationType>";
# FIXME: Change the default name (used for callback functions) to something other than handleEvent. It makes little sense.
my $functionName = $operation->name || "handleEvent";
my $functionImplementationName = $operation->extendedAttributes->{ImplementedAs} || $functionName;
my @arguments = ();
my $thisValue = "jsUndefined()";
my $callbackThisObject = $operation->extendedAttributes->{CallbackThisObject};
if ($callbackThisObject) {
my $thisObjectType = $codeGenerator->ParseType($callbackThisObject);
AddToIncludesForIDLType($thisObjectType, $includesRef, 1);
my $IDLType = GetIDLType($interfaceOrCallback, $thisObjectType);
push(@arguments, "typename ${IDLType}::ParameterType thisObject");
my $thisObjectArgument = IDLArgument->new();
$thisObjectArgument->type($thisObjectType);
$thisValue = NativeToJSValueUsingReferences($thisObjectArgument, $interfaceOrCallback, "thisObject", "globalObject");
}
foreach my $argument (@{$operation->arguments}) {
AddToIncludesForIDLType($argument->type, $includesRef, 1);
my $IDLType = GetIDLType($interfaceOrCallback, $argument->type);
push(@arguments, "typename ${IDLType}::ParameterType " . $argument->name);
}
push(@$contentRef, "${nativeReturnType} ${className}::${functionImplementationName}(" . join(", ", @arguments) . ")\n");
push(@$contentRef, "{\n");
# FIXME: This is needed for NodeFilter, which works even for disconnected iframes. We should investigate
# if that behavior is needed for other callbacks.
if (!$operation->extendedAttributes->{SkipCallbackInvokeCheck}) {
push(@$contentRef, " if (!canInvokeCallback())\n");
push(@$contentRef, " return CallbackResultType::UnableToExecute;\n\n");
}
push(@$contentRef, " Ref<$className> protectedThis(*this);\n\n");
push(@$contentRef, " auto& globalObject = *m_data->globalObject();\n");
push(@$contentRef, " auto& vm = globalObject.vm();\n\n");
push(@$contentRef, " JSLockHolder lock(vm);\n");
push(@$contentRef, " auto& lexicalGlobalObject = globalObject;\n");
push(@$contentRef, " JSValue thisValue = ${thisValue};\n");
push(@$contentRef, " MarkedArgumentBuffer args;\n");
foreach my $argument (@{$operation->arguments}) {
push(@$contentRef, " args.append(" . NativeToJSValueUsingReferences($argument, $interfaceOrCallback, $argument->name, "globalObject") . ");\n");
}
push(@$contentRef, " ASSERT(!args.hasOverflowed());\n");
push(@$contentRef, "\n NakedPtr<JSC::Exception> returnedException;\n");
my $callbackInvocation;
if (ref($interfaceOrCallback) eq "IDLCallbackFunction") {
$callbackInvocation = "m_data->invokeCallback(thisValue, args, JSCallbackData::CallbackType::Function, Identifier(), returnedException)";
} else {
my $callbackType = $numOperations > 1 ? "Object" : "FunctionOrObject";
$callbackInvocation = "m_data->invokeCallback(thisValue, args, JSCallbackData::CallbackType::${callbackType}, Identifier::fromString(vm, \"${functionName}\"), returnedException)";
}
if ($operation->type->name eq "undefined") {
push(@$contentRef, " ${callbackInvocation};\n");
} else {
push(@$contentRef, " auto jsResult = ${callbackInvocation};\n");
}
$includesRef->{"JSDOMExceptionHandling.h"} = 1;
push(@$contentRef, " if (returnedException) {\n");
if ($operation->extendedAttributes->{RethrowException}) {
push(@$contentRef, " auto throwScope = DECLARE_THROW_SCOPE(vm);\n");
push(@$contentRef, " throwException(&lexicalGlobalObject, throwScope, returnedException);\n");
} else {
push(@$contentRef, " reportException(&lexicalGlobalObject, returnedException);\n");
}
push(@$contentRef, " return CallbackResultType::ExceptionThrown;\n");
push(@$contentRef, " }\n\n");
if ($operation->type->name eq "undefined") {
push(@$contentRef, " return { };\n");
} else {
my $nativeValue = JSValueToNative($interfaceOrCallback, $operation, "jsResult", "", "&lexicalGlobalObject", "lexicalGlobalObject");
push(@$contentRef, " auto throwScope = DECLARE_THROW_SCOPE(vm);\n");
push(@$contentRef, " auto returnValue = ${nativeValue};\n");
push(@$contentRef, " RETURN_IF_EXCEPTION(throwScope, CallbackResultType::ExceptionThrown);\n");
push(@$contentRef, " return { WTFMove(returnValue) };\n");
}
push(@$contentRef, "}\n\n");
}
}
if ($interfaceOrCallback->extendedAttributes->{IsWeakCallback}) {
push(@$contentRef, "void ${className}::visitJSFunction(JSC::AbstractSlotVisitor& visitor)\n");
push(@$contentRef, "{\n");
push(@$contentRef, " m_data->visitJSFunction(visitor);\n");
push(@$contentRef, "}\n\n");
push(@$contentRef, "void ${className}::visitJSFunction(JSC::SlotVisitor& visitor)\n");
push(@$contentRef, "{\n");
push(@$contentRef, " m_data->visitJSFunction(visitor);\n");
push(@$contentRef, "}\n\n");
}
push(@$contentRef, "JSC::JSValue toJS(${name}& impl)\n");
push(@$contentRef, "{\n");
push(@$contentRef, " if (!static_cast<${className}&>(impl).callbackData())\n");
push(@$contentRef, " return jsNull();\n\n");
push(@$contentRef, " return static_cast<${className}&>(impl).callbackData()->callback();\n");
push(@$contentRef, "}\n\n");
}
sub GenerateWriteBarriersForArguments
{
my ($outputArray, $operation, $indent, $isDryRun) = @_;
my $argumentIndex = 0;
my $hasOutput = 0;
foreach my $argument (@{$operation->arguments}) {
if ($argument->type->name eq "EventListener") {
push(@$outputArray, $indent . "vm.heap.writeBarrier(&static_cast<JSObject&>(*castedThis), argument${argumentIndex}.value());\n") if !$isDryRun;
$hasOutput = 1;
}
$argumentIndex++;
}
return $hasOutput;
}
sub GenerateImplementationFunctionCall
{
my ($outputArray, $operation, $interface, $functionString, $indent, $hasThrowScope) = @_;
my $callTracer = $operation->extendedAttributes->{CallTracer} || $interface->extendedAttributes->{CallTracer};
if ($callTracer) {
my @callTracerArguments = map { $_->name } @{$operation->arguments};
GenerateCallTracer($outputArray, $callTracer, $operation->name, \@callTracerArguments, $indent);
}
my $dryRun = 1;
my $hasWriteBarriersForArguments = GenerateWriteBarriersForArguments($outputArray, $operation, $indent, $dryRun);
my $returnArgumentName = GetOperationReturnedArgumentName($operation);
if ($returnArgumentName) {
push(@$outputArray, $indent . "invokeFunctorPropagatingExceptionIfNecessary(*lexicalGlobalObject, throwScope, [&] { return $functionString; });\n");
GenerateWriteBarriersForArguments($outputArray, $operation, $indent);
push(@$outputArray, $indent . "return JSValue::encode($returnArgumentName.value());\n");
} else {
my $globalObjectReference = $operation->isStatic ? "*jsCast<JSDOMGlobalObject*>(lexicalGlobalObject)" : "*castedThis->globalObject()";
if ($hasWriteBarriersForArguments) {
push(@$outputArray, $indent . "auto result = JSValue::encode(" . NativeToJSValueUsingPointers($operation, $interface, $functionString, $globalObjectReference) . ");\n");
push(@$outputArray, $indent . "RETURN_IF_EXCEPTION(throwScope, encodedJSValue());\n") if $hasThrowScope;
GenerateWriteBarriersForArguments($outputArray, $operation, $indent);
push(@$outputArray, $indent . "return result;\n");
} else {
push(@$outputArray, $indent . "RELEASE_AND_RETURN(throwScope, JSValue::encode(" . NativeToJSValueUsingPointers($operation, $interface, $functionString, $globalObjectReference) . "));\n");
}
}
}
sub GenerateImplementationCustomFunctionCall
{
my ($outputArray, $operation, $interface, $className, $functionImplementationName, $indent) = @_;
my @customFunctionArguments = ();
push(@customFunctionArguments, "*lexicalGlobalObject");
push(@customFunctionArguments, "*callFrame");
push(@customFunctionArguments, "WTFMove(promise)") if $codeGenerator->IsPromiseType($operation->type) && !$operation->extendedAttributes->{ReturnsOwnPromise};
if ($operation->isStatic) {
push(@$outputArray, $indent . "RELEASE_AND_RETURN(throwScope, (JSValue::encode(${className}::" . $functionImplementationName . "(" . join(", ", @customFunctionArguments) . "))));\n");
} else {
push(@$outputArray, $indent . "RELEASE_AND_RETURN(throwScope, (JSValue::encode(castedThis->" . $functionImplementationName . "(" . join(", ", @customFunctionArguments) . "))));\n");
}
}
sub IsValueIterableInterface
{
my $interface = shift;
return 0 unless $interface->iterable;
return 0 if length $interface->iterable->keyType;
# FIXME: See https://webkit.org/b/159140, we should die if the next check is false.
return 0 unless GetIndexedGetterOperation($interface);
return 1;
}
sub IsKeyValueIterableInterface
{
my $interface = shift;
return 0 unless $interface->iterable;
return 0 if IsValueIterableInterface($interface);
return 1;
}
sub GenerateIterableDefinition
{
my $interface = shift;
my $interfaceName = $interface->type->name;
my $className = "JS$interfaceName";
my $visibleInterfaceName = $codeGenerator->GetVisibleInterfaceName($interface);
AddToImplIncludes("JSDOMIterator.h");
AddToImplIncludes("<JavaScriptCore/SlotVisitorMacros.h>");
return unless IsKeyValueIterableInterface($interface);
my $iteratorName = "${interfaceName}Iterator";
my $iteratorPrototypeName = "${interfaceName}IteratorPrototype";
my $iteratorTraitsName = "${interfaceName}IteratorTraits";
my $iteratorTraitsType = $interface->iterable->isKeyValue ? "JSDOMIteratorType::Map" : "JSDOMIteratorType::Set";
my $iteratorTraitsKeyType = $interface->iterable->isKeyValue ? GetIDLType($interface, $interface->iterable->keyType) : "void";
my $iteratorTraitsValueType = GetIDLType($interface, $interface->iterable->valueType);
AddToImplIncludesForIDLType($interface->iterable->keyType) if $interface->iterable->isKeyValue;
AddToImplIncludesForIDLType($interface->iterable->valueType);
push(@implContent, <<END);
struct ${iteratorTraitsName} {
static constexpr JSDOMIteratorType type = ${iteratorTraitsType};
using KeyType = ${iteratorTraitsKeyType};
using ValueType = ${iteratorTraitsValueType};
};
using ${iteratorName}Base = JSDOMIteratorBase<${className}, ${iteratorTraitsName}>;
class ${iteratorName} final : public ${iteratorName}Base {
public:
using Base = ${iteratorName}Base;
DECLARE_INFO;
template<typename, JSC::SubspaceAccess mode> static JSC::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
auto& clientData = *static_cast<JSVMClientData*>(vm.clientData);
auto& spaces = clientData.subspaces();
if (auto* space = spaces.m_subspaceFor${iteratorName}.get())
return space;
static_assert(std::is_base_of_v<JSC::JSDestructibleObject, ${iteratorName}> || !${iteratorName}::needsDestruction);
if constexpr (std::is_base_of_v<JSC::JSDestructibleObject, ${iteratorName}>)
spaces.m_subspaceFor${iteratorName} = makeUnique<IsoSubspace> ISO_SUBSPACE_INIT(vm.heap, vm.destructibleObjectHeapCellType.get(), ${iteratorName});
else
spaces.m_subspaceFor${iteratorName} = makeUnique<IsoSubspace> ISO_SUBSPACE_INIT(vm.heap, vm.cellHeapCellType.get(), ${iteratorName});
auto* space = spaces.m_subspaceFor${iteratorName}.get();
IGNORE_WARNINGS_BEGIN(\"unreachable-code\")
IGNORE_WARNINGS_BEGIN(\"tautological-compare\")
void (*myVisitOutputConstraint)(JSC::JSCell*, JSC::SlotVisitor&) = ${iteratorName}::visitOutputConstraints;
void (*jsCellVisitOutputConstraint)(JSC::JSCell*, JSC::SlotVisitor&) = JSC::JSCell::visitOutputConstraints;
if (myVisitOutputConstraint != jsCellVisitOutputConstraint)
clientData.outputConstraintSpaces().append(space);
IGNORE_WARNINGS_END
IGNORE_WARNINGS_END
return space;
}
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
static ${iteratorName}* create(JSC::VM& vm, JSC::Structure* structure, ${className}& iteratedObject, IterationKind kind)
{
auto* instance = new (NotNull, JSC::allocateCell<${iteratorName}>(vm.heap)) ${iteratorName}(structure, iteratedObject, kind);
instance->finishCreation(vm);
return instance;
}
private:
${iteratorName}(JSC::Structure* structure, ${className}& iteratedObject, IterationKind kind)
: Base(structure, iteratedObject, kind)
{
}
};
using ${iteratorPrototypeName} = JSDOMIteratorPrototype<${className}, ${iteratorTraitsName}>;
JSC_ANNOTATE_HOST_FUNCTION(${iteratorPrototypeName}Next, ${iteratorPrototypeName}::next);
template<>
const JSC::ClassInfo ${iteratorName}Base::s_info = { "${visibleInterfaceName} Iterator", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(${iteratorName}Base) };
const JSC::ClassInfo ${iteratorName}::s_info = { "${visibleInterfaceName} Iterator", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(${iteratorName}) };
template<>
const JSC::ClassInfo ${iteratorPrototypeName}::s_info = { "${visibleInterfaceName} Iterator", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(${iteratorPrototypeName}) };
END
foreach my $operation (@{$interface->iterable->operations}) {
my $propertyName = $operation->name;
my $functionName = GetFunctionName($interface, $className, $operation);
next if $propertyName eq "[Symbol.Iterator]";
if ($propertyName eq "forEach") {
push(@implContent, <<END);
static inline EncodedJSValue ${functionName}Caller(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame, JS$interfaceName* thisObject)
{
return JSValue::encode(iteratorForEach<${iteratorName}>(*lexicalGlobalObject, *callFrame, *thisObject));
}
END
} else {
my $iterationKind = "Entries";
$iterationKind = "Keys" if $propertyName eq "keys";
$iterationKind = "Values" if $propertyName eq "values";
$iterationKind = "Values" if $propertyName eq "entries" and not $interface->iterable->isKeyValue;
push(@implContent, <<END);
static inline EncodedJSValue ${functionName}Caller(JSGlobalObject*, CallFrame*, JS$interfaceName* thisObject)
{
return JSValue::encode(iteratorCreate<${iteratorName}>(*thisObject, IterationKind::${iterationKind}));
}
END
}
push(@implContent, <<END);
JSC_DEFINE_HOST_FUNCTION(${functionName}, (JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame))
{
return IDLOperation<${className}>::call<${functionName}Caller>(*lexicalGlobalObject, *callFrame, "${propertyName}");
}
END
}
}
# http://heycam.github.io/webidl/#dfn-flattened-union-member-types
sub GetFlattenedMemberTypes
{
my ($idlUnionType) = @_;
my @flattenedMemberTypes = ();
foreach my $memberType (@{$idlUnionType->subtypes}) {
if ($memberType->isUnion) {
push(@flattenedMemberTypes, GetFlattenedMemberTypes($memberType));
} else {
push(@flattenedMemberTypes, $memberType);
}
}
return @flattenedMemberTypes;
}
# http://heycam.github.io/webidl/#dfn-number-of-nullable-member-types
sub GetNumberOfNullableMemberTypes
{
my ($idlUnionType) = @_;
my $count = 0;
foreach my $memberType (@{$idlUnionType->subtypes}) {
$count++ if $memberType->isNullable;
$count += GetNumberOfNullableMemberTypes($memberType) if $memberType->isUnion;
}
return $count;
}
sub GetIDLUnionMemberTypes
{
my ($interface, $idlUnionType) = @_;
my $numberOfNullableMembers = GetNumberOfNullableMemberTypes($idlUnionType);
assert("Union types must only have 0 or 1 nullable types.") if $numberOfNullableMembers > 1;
my @idlUnionMemberTypes = ();
push(@idlUnionMemberTypes, "IDLNull") if $numberOfNullableMembers == 1;
foreach my $memberType (GetFlattenedMemberTypes($idlUnionType)) {
push(@idlUnionMemberTypes, GetIDLTypeExcludingNullability($interface, $memberType));
}
return @idlUnionMemberTypes;
}
sub IsAnnotatedType
{
my ($type) = @_;
return 1 if $type->extendedAttributes->{Clamp};
return 1 if $type->extendedAttributes->{EnforceRange};
return 1 if $type->extendedAttributes->{LegacyNullToEmptyString};
return 1 if $type->extendedAttributes->{AtomString};
return 1 if $type->extendedAttributes->{RequiresExistingAtomString};
return 1 if $type->extendedAttributes->{AllowShared};
}
sub GetAnnotatedIDLType
{
my ($type) = @_;
return "IDLClampAdaptor" if $type->extendedAttributes->{Clamp};
return "IDLEnforceRangeAdaptor" if $type->extendedAttributes->{EnforceRange};
return "IDLLegacyNullToEmptyStringAdaptor" if $type->extendedAttributes->{LegacyNullToEmptyString};
return "IDLAtomStringAdaptor" if $type->extendedAttributes->{AtomString};
return "IDLRequiresExistingAtomStringAdaptor" if $type->extendedAttributes->{RequiresExistingAtomString};
return "IDLAllowSharedAdaptor" if $type->extendedAttributes->{AllowShared};
}
sub GetBaseIDLType
{
my ($interface, $type) = @_;
if ($type->extendedAttributes->{OverrideIDLType}) {
return $type->extendedAttributes->{OverrideIDLType};
}
my %IDLTypes = (
"undefined" => "IDLUndefined",
"any" => "IDLAny",
"boolean" => "IDLBoolean",
"byte" => "IDLByte",
"octet" => "IDLOctet",
"short" => "IDLShort",
"unsigned short" => "IDLUnsignedShort",
"long" => "IDLLong",
"unsigned long" => "IDLUnsignedLong",
"long long" => "IDLLongLong",
"unsigned long long" => "IDLUnsignedLongLong",
"float" => "IDLFloat",
"unrestricted float" => "IDLUnrestrictedFloat",
"double" => "IDLDouble",
"unrestricted double" => "IDLUnrestrictedDouble",
"DOMString" => "IDLDOMString",
"ByteString" => "IDLByteString",
"USVString" => "IDLUSVString",
"object" => "IDLObject",
"ArrayBuffer" => "IDLArrayBuffer",
"ArrayBufferView" => "IDLArrayBufferView",
"DataView" => "IDLDataView",
"Int8Array" => "IDLInt8Array",
"Int16Array" => "IDLInt16Array",
"Int32Array" => "IDLInt32Array",
"Uint8Array" => "IDLUint8Array",
"Uint16Array" => "IDLUint16Array",
"Uint32Array" => "IDLUint32Array",
"Uint8ClampedArray" => "IDLUint8ClampedArray",
"Float32Array" => "IDLFloat32Array",
"Float64Array" => "IDLFloat64Array",
"BigInt64Array" => "IDLBigInt64Array",
"BigUint64Array" => "IDLBigUint64Array",
# Non-WebIDL extensions
"Date" => "IDLDate",
"EventListener" => "IDLEventListener<JSEventListener>",
"JSON" => "IDLJSON",
"ScheduledAction" => "IDLScheduledAction",
"SerializedScriptValue" => "IDLSerializedScriptValue<SerializedScriptValue>",
);
return $IDLTypes{$type->name} if exists $IDLTypes{$type->name};
return "IDLEnumeration<" . GetEnumerationClassName($type, $interface) . ">" if $codeGenerator->IsEnumType($type);
return "IDLDictionary<" . GetDictionaryClassName($type, $interface) . ">" if $codeGenerator->IsDictionaryType($type);
return "IDLSequence<" . GetIDLType($interface, @{$type->subtypes}[0]) . ">" if $codeGenerator->IsSequenceType($type);
return "IDLFrozenArray<" . GetIDLType($interface, @{$type->subtypes}[0]) . ">" if $codeGenerator->IsFrozenArrayType($type);
return "IDLRecord<" . GetIDLType($interface, @{$type->subtypes}[0]) . ", " . GetIDLType($interface, @{$type->subtypes}[1]) . ">" if $codeGenerator->IsRecordType($type);
return "IDLPromise<" . GetIDLType($interface, @{$type->subtypes}[0]) . ">" if $codeGenerator->IsPromiseType($type);
return "IDLUnion<" . join(", ", GetIDLUnionMemberTypes($interface, $type)) . ">" if $type->isUnion;
return "IDLCallbackFunction<" . GetCallbackClassName($type->name) . ">" if $codeGenerator->IsCallbackFunction($type);
return "IDLCallbackInterface<" . GetCallbackClassName($type->name) . ">" if $codeGenerator->IsCallbackInterface($type);
assert("Unknown type '" . $type->name . "'.\n") unless $codeGenerator->IsInterfaceType($type);
return "IDLInterface<" . $type->name . ">";
}
sub GetIDLTypeExcludingNullability
{
my ($interface, $type) = @_;
my $baseIDLType = GetBaseIDLType($interface, $type);
$baseIDLType = GetAnnotatedIDLType($type) . "<" . $baseIDLType . ">" if IsAnnotatedType($type);
return $baseIDLType;
}
sub GetIDLType
{
my ($interface, $type) = @_;
my $baseIDLType = GetIDLTypeExcludingNullability($interface, $type);
$baseIDLType = "IDLNullable<" . $baseIDLType . ">" if $type->isNullable;
return $baseIDLType;
}
sub ShouldPassArgumentByReference
{
my ($argument) = @_;
my $type = $argument->type;
return 0 if $type->isNullable;
return 0 if $codeGenerator->IsCallbackInterface($type);
return 0 if $codeGenerator->IsCallbackFunction($type);
return 0 if !$codeGenerator->IsWrapperType($type) && !$codeGenerator->IsBufferSourceType($type);
return 1;
}
sub JSValueToNativeDOMConvertNeedsThisObject
{
my $type = shift;
return 1 if $type->name eq "EventListener";
return 0;
}
sub JSValueToNativeDOMConvertNeedsGlobalObject
{
my $type = shift;
return 1 if $codeGenerator->IsCallbackInterface($type);
return 1 if $codeGenerator->IsCallbackFunction($type);
return JSValueToNativeDOMConvertNeedsGlobalObject(@{$type->subtypes}[1]) if $codeGenerator->IsRecordType($type);
return 1 if $type->name eq "ScheduledAction";
return 0;
}
sub IsValidContextForJSValueToNative
{
my $context = shift;
return (ref($context) eq "IDLAttribute" && !$codeGenerator->IsEnumType($context->type)) || ref($context) eq "IDLArgument" || ref($context) eq "IDLDictionaryMember" || ref($context) eq "IDLOperation";
}
sub JSValueToNative
{
my ($interface, $context, $value, $conditional, $lexicalGlobalObjectPointer, $lexicalGlobalObjectReference, $thisObjectReference, $globalObjectReference, $exceptionThrower) = @_;
assert("Invalid context type") if !IsValidContextForJSValueToNative($context);
my $type = $context->type;
# FIXME: Remove these 3 variables when all JSValueToNative use references.
$lexicalGlobalObjectPointer = "lexicalGlobalObject" unless $lexicalGlobalObjectPointer;
$lexicalGlobalObjectReference = "*lexicalGlobalObject" unless $lexicalGlobalObjectReference;
$thisObjectReference = "*castedThis" unless $thisObjectReference;
AddToImplIncludesForIDLType($type, $conditional);
AddToImplIncludes("JSDOMGlobalObject.h", $conditional) if JSValueToNativeDOMConvertNeedsGlobalObject($type);
my $IDLType = GetIDLType($interface, $type);
my @conversionArguments = ();
push(@conversionArguments, $lexicalGlobalObjectReference);
push(@conversionArguments, $value);
push(@conversionArguments, $thisObjectReference) if JSValueToNativeDOMConvertNeedsThisObject($type);
push(@conversionArguments, $globalObjectReference) if JSValueToNativeDOMConvertNeedsGlobalObject($type);
push(@conversionArguments, $exceptionThrower) if $exceptionThrower;
return "convert<$IDLType>(" . join(", ", @conversionArguments) . ")";
}
sub ToNativeForFunctionWithoutTypeCheck
{
my ($interface, $context, $value, $conditional, $lexicalGlobalObjectPointer, $lexicalGlobalObjectReference, $thisObjectReference) = @_;
assert("Invalid context type") if !IsValidContextForJSValueToNative($context);
my $type = $context->type;
# FIXME: Remove these 3 variables when all JSValueToNative use references.
$lexicalGlobalObjectPointer = "lexicalGlobalObject" unless $lexicalGlobalObjectPointer;
$lexicalGlobalObjectReference = "*lexicalGlobalObject" unless $lexicalGlobalObjectReference;
$thisObjectReference = "*castedThis" unless $thisObjectReference;
AddToImplIncludesForIDLType($type, $conditional);
# FIXME: Support more types.
AddToImplIncludes("DOMJITIDLConvert.h");
my $IDLType = GetIDLType($interface, $type);
my @conversionArguments = ();
push(@conversionArguments, "$lexicalGlobalObjectReference");
push(@conversionArguments, "$value");
return ("DOMJIT::DirectConverter<$IDLType>::directConvert(" . join(", ", @conversionArguments) . ")", 1);
}
sub NativeToJSValueDOMConvertNeedsState
{
my ($type) = @_;
# FIXME: We need a more robust way to specify this requirement so as not
# to require specializing each type. Perhaps just requiring all override
# types to take both lexicalGlobalObject and the global object would work?
if ($type->extendedAttributes->{OverrideIDLType}) {
my $overrideTypeName = $type->extendedAttributes->{OverrideIDLType};
return 1 if $overrideTypeName eq "IDLIDBKey";
return 1 if $overrideTypeName eq "IDLWebGLAny";
return 1 if $overrideTypeName eq "IDLWebGLExtension";
return 0;
}
# FIXME: This should actually check if all the sub-objects of the union need the lexicalGlobalObject.
return 1 if $type->isUnion;
return 1 if $codeGenerator->IsSequenceOrFrozenArrayType($type);
return 1 if $codeGenerator->IsRecordType($type);
return 1 if $codeGenerator->IsStringType($type);
return 1 if $codeGenerator->IsEnumType($type);
return 1 if $codeGenerator->IsDictionaryType($type);
return 1 if $codeGenerator->IsInterfaceType($type);
return 1 if $codeGenerator->IsBufferSourceType($type);
return 1 if $codeGenerator->IsPromiseType($type);
return 1 if $type->name eq "Date";
return 1 if $type->name eq "JSON";
return 1 if $type->name eq "SerializedScriptValue";
return 0;
}
sub NativeToJSValueDOMConvertNeedsGlobalObject
{
my ($type) = @_;
# FIXME: We need a more robust way to specify this requirement so as not
# to require specializing each type. Perhaps just requiring all override
# types to take both lexicalGlobalObject and the global object would work?
if ($type->extendedAttributes->{OverrideIDLType}) {
my $overrideTypeName = $type->extendedAttributes->{OverrideIDLType};
return 1 if $overrideTypeName eq "IDLIDBKey";
return 1 if $overrideTypeName eq "IDLWebGLAny";
return 1 if $overrideTypeName eq "IDLWebGLExtension";
return 0;
}
# FIXME: This should actually check if all the sub-objects of the union need the global object.
return 1 if $type->isUnion;
return 1 if $codeGenerator->IsSequenceOrFrozenArrayType($type);
return 1 if $codeGenerator->IsRecordType($type);
return 1 if $codeGenerator->IsDictionaryType($type);
return 1 if $codeGenerator->IsInterfaceType($type);
return 1 if $codeGenerator->IsBufferSourceType($type);
return 1 if $codeGenerator->IsPromiseType($type);
return 1 if $type->name eq "SerializedScriptValue";
return 0;
}
sub NativeToJSValueUsingReferences
{
my ($context, $interface, $value, $globalObjectReference) = @_;
return NativeToJSValue($context, $interface, $value, "lexicalGlobalObject", $globalObjectReference);
}
# FIXME: We should remove NativeToJSValueUsingPointers and combine NativeToJSValueUsingReferences and NativeToJSValue
sub NativeToJSValueUsingPointers
{
my ($context, $interface, $value, $globalObjectReference) = @_;
return NativeToJSValue($context, $interface, $value, "*lexicalGlobalObject", $globalObjectReference);
}
sub IsValidContextForNativeToJSValue
{
my $context = shift;
return ref($context) eq "IDLAttribute" || ref($context) eq "IDLArgument" || ref($context) eq "IDLDictionaryMember" || ref($context) eq "IDLOperation";
}
sub NativeToJSValueMayThrow
{
my ($context) = @_;
return ref($context) eq "IDLAttribute" || ref($context) eq "IDLOperation" || ref($context) eq "IDLDictionaryMember";
}
sub NativeToJSValue
{
my ($context, $interface, $value, $lexicalGlobalObjectReference, $globalObjectReference) = @_;
assert("Invalid context type") if !IsValidContextForNativeToJSValue($context);
my $conditional = $context->extendedAttributes->{Conditional};
my $type = $context->type;
my $mayThrowException = NativeToJSValueMayThrow($context);
# We could instead overload a function to work with optional as well as non-optional numbers, but this
# is slightly better because it guarantees we will fail to compile if the IDL file doesn't match the C++.
if ($context->extendedAttributes->{Reflect} and ($type->name eq "unsigned long" or $type->name eq "unsigned short")) {
$value =~ s/getUnsignedIntegralAttribute/getIntegralAttribute/g;
$value = "std::max(0, $value)";
}
AddToImplIncludesForIDLType($type, $conditional);
AddToImplIncludes("JSDOMGlobalObject.h", $conditional) if NativeToJSValueDOMConvertNeedsGlobalObject($type);
if ($context->extendedAttributes->{CheckSecurityForNode}) {
AddToImplIncludes("JSDOMBindingSecurity.h", $conditional);
$value = "BindingSecurity::checkSecurityForNode($lexicalGlobalObjectReference, $value)";
}
my $IDLType = GetIDLType($interface, $type);
# FIXME: Not all promise types require the functor wrapping (some attribute getters actually return a value)
# but wrapping is a no-op, so fixing this would purely be a stylistic / compile time fix.
my $needsFunctorWrapping = $type->name eq "undefined" || $codeGenerator->IsPromiseType($type);
my @conversionArguments = ();
push(@conversionArguments, $lexicalGlobalObjectReference) if NativeToJSValueDOMConvertNeedsState($type) || $mayThrowException;
push(@conversionArguments, $globalObjectReference) if NativeToJSValueDOMConvertNeedsGlobalObject($type);
push(@conversionArguments, "throwScope") if $mayThrowException;
if ($needsFunctorWrapping) {
push(@conversionArguments, "[&]() -> decltype(auto) { return $value; }");
} else {
push(@conversionArguments, "$value");
}
my $functionName = $context->extendedAttributes->{NewObject} ? "toJSNewlyCreated" : "toJS";
return "${functionName}<${IDLType}>(" . join(", ", @conversionArguments) . ")";
}
sub ceilingToPowerOf2
{
my ($size) = @_;
my $powerOf2 = 1;
while ($size > $powerOf2) {
$powerOf2 <<= 1;
}
return $powerOf2;
}
# Internal Helper
sub GenerateHashTableValueArray
{
my $keys = shift;
my $specials = shift;
my $value1 = shift;
my $value2 = shift;
my $conditionals = shift;
my $readWriteConditionals = shift;
my $nameEntries = shift;
my $packedSize = scalar @{$keys};
push(@implContent, "\nstatic const HashTableValue $nameEntries\[\] =\n\{\n");
my $hasSetter = "false";
my $i = 0;
foreach my $key (@{$keys}) {
my $firstTargetType;
my $secondTargetType = "";
my $conditional;
if ($conditionals) {
$conditional = $conditionals->{$key};
}
if ($conditional) {
my $conditionalString = $codeGenerator->GenerateConditionalStringFromAttributeValue($conditional);
push(@implContent, "#if ${conditionalString}\n");
}
if ("@$specials[$i]" =~ m/DOMJITFunction/) {
$firstTargetType = "static_cast<RawNativeFunction>";
$secondTargetType = "static_cast<const JSC::DOMJIT::Signature*>";
} elsif ("@$specials[$i]" =~ m/Function/) {
$firstTargetType = "static_cast<RawNativeFunction>";
} elsif ("@$specials[$i]" =~ m/Builtin/) {
$firstTargetType = "static_cast<BuiltinGenerator>";
} elsif ("@$specials[$i]" =~ m/ConstantInteger/) {
$firstTargetType = "";
} elsif ("@$specials[$i]" =~ m/DOMJITAttribute/) {
$firstTargetType = "static_cast<const JSC::DOMJIT::GetterSetter*>";
} else {
$firstTargetType = "static_cast<PropertySlot::GetValueFunc>";
$secondTargetType = "static_cast<PutPropertySlot::PutValueFunc>";
$hasSetter = "true";
}
if ("@$specials[$i]" =~ m/ConstantInteger/) {
push(@implContent, " { \"$key\", @$specials[$i], NoIntrinsic, { (long long)" . $firstTargetType . "(@$value1[$i]) } },\n");
} else {
my $readWriteConditional = $readWriteConditionals ? $readWriteConditionals->{$key} : undef;
if ($readWriteConditional) {
my $readWriteConditionalString = $codeGenerator->GenerateConditionalStringFromAttributeValue($readWriteConditional);
push(@implContent, "#if ${readWriteConditionalString}\n");
}
push(@implContent, " { \"$key\", @$specials[$i], NoIntrinsic, { (intptr_t)" . $firstTargetType . "(@$value1[$i]), (intptr_t) " . $secondTargetType . "(@$value2[$i]) } },\n");
if ($readWriteConditional) {
push(@implContent, "#else\n") ;
push(@implContent, " { \"$key\", JSC::PropertyAttribute::ReadOnly | @$specials[$i], NoIntrinsic, { (intptr_t)" . $firstTargetType . "(@$value1[$i]), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } },\n");
push(@implContent, "#endif\n");
}
}
if ($conditional) {
push(@implContent, "#else\n");
push(@implContent, " { 0, 0, NoIntrinsic, { 0, 0 } },\n");
push(@implContent, "#endif\n");
}
++$i;
}
push(@implContent, " { 0, 0, NoIntrinsic, { 0, 0 } }\n") if (!$packedSize);
push(@implContent, "};\n\n");
return $hasSetter;
}
sub GenerateHashTable
{
my $object = shift;
my $className = shift;
my $name = shift;
my $size = shift;
my $keys = shift;
my $specials = shift;
my $value1 = shift;
my $value2 = shift;
my $conditionals = shift;
my $readWriteConditionals = shift;
my $justGenerateValueArray = shift;
my $nameEntries = "${name}Values";
$nameEntries =~ s/:/_/g;
my $nameIndex = "${name}Index";
$nameIndex =~ s/:/_/g;
if (($name =~ /Prototype/) or ($name =~ /Constructor/)) {
my $type = $name;
my $implClass;
if ($name =~ /Prototype/) {
$type =~ s/Prototype.*//;
$implClass = $type; $implClass =~ s/Wrapper$//;
push(@implContent, "/* Hash table for prototype */\n");
} else {
$type =~ s/Constructor.*//;
$implClass = $type; $implClass =~ s/Constructor$//;
push(@implContent, "/* Hash table for constructor */\n");
}
} else {
push(@implContent, "/* Hash table */\n");
}
if ($justGenerateValueArray) {
GenerateHashTableValueArray($keys, $specials, $value1, $value2, $conditionals, $readWriteConditionals, $nameEntries) if $size;
return;
}
# Generate size data for compact' size hash table
my @table = ();
my @links = ();
my $compactSize = ceilingToPowerOf2($size * 2);
my $maxDepth = 0;
my $collisions = 0;
my $numEntries = $compactSize;
my $i = 0;
foreach (@{$keys}) {
my $depth = 0;
my $h = Hasher::GenerateHashValue($_) % $numEntries;
while (defined($table[$h])) {
if (defined($links[$h])) {
$h = $links[$h];
$depth++;
} else {
$collisions++;
$links[$h] = $compactSize;
$h = $compactSize;
$compactSize++;
}
}
$table[$h] = $i;
$i++;
$maxDepth = $depth if ($depth > $maxDepth);
}
push(@implContent, "\nstatic const struct CompactHashIndex ${nameIndex}\[$compactSize\] = {\n");
for (my $i = 0; $i < $compactSize; $i++) {
my $T = -1;
if (defined($table[$i])) { $T = $table[$i]; }
my $L = -1;
if (defined($links[$i])) { $L = $links[$i]; }
push(@implContent, " { $T, $L },\n");
}
push(@implContent, "};\n\n");
# Dump the hash table
my $hasSetter = GenerateHashTableValueArray($keys, $specials, $value1, $value2, $conditionals, $readWriteConditionals, $nameEntries);
my $packedSize = scalar @{$keys};
my $compactSizeMask = $numEntries - 1;
push(@implContent, "static const HashTable $name = { $packedSize, $compactSizeMask, $hasSetter, ${className}::info(), $nameEntries, $nameIndex };\n");
}
sub SubstituteHeader
{
# Internal macOS SDKs up to 10.15 have non-suffixed headers in their WebKitAdditions, requiring the addition of the suffix to build successfully.
my $include = shift;
if ($include eq "\"ApplePayInstallmentConfiguration.h\"") {
return "\"ApplePayInstallmentConfigurationWebCore.h\"";
}
if ($include eq "\"ApplePaySetupFeatureType.h\"") {
return "\"ApplePaySetupFeatureTypeWebCore.h\"";
}
if ($include eq "\"ApplePaySetupFeature.h\"") {
return "\"ApplePaySetupFeatureWebCore.h\"";
}
if ($include eq "\"ApplePaySetup.h\"") {
return "\"ApplePaySetupWebCore.h\"";
}
if ($include eq "\"PaymentInstallmentConfiguration.h\"") {
return "\"PaymentInstallmentConfigurationWebCore.h\"";
}
return $include;
}
sub WriteData
{
my $object = shift;
my $interface = shift;
my $outputDir = shift;
my $name = $interface->type->name;
my $headerFileName = "$outputDir/JS$name.h";
my $implFileName = "$outputDir/JS$name.cpp";
my $depsFileName = "$outputDir/JS$name.dep";
# Update a .cpp file if the contents are changed.
my $contents = join "", @implContentHeader;
my @includes = ();
my %implIncludeConditions = ();
foreach my $include (keys %implIncludes) {
next if $headerIncludes{$include};
next if $headerTrailingIncludes{$include};
my $condition = $implIncludes{$include};
my $checkType = $include;
$checkType =~ s/\.h//;
next if $codeGenerator->IsSVGAnimatedTypeName($checkType);
$include = "\"$include\"" unless $include =~ /^["<]/; # "
if ($condition eq 1) {
push @includes, $include;
} else {
push @{$implIncludeConditions{$codeGenerator->GenerateConditionalStringFromAttributeValue($condition)}}, $include;
}
}
foreach my $include (sort @includes) {
$contents .= "#include $include\n";
}
foreach my $condition (sort keys %implIncludeConditions) {
$contents .= "\n#if " . $condition . "\n";
foreach my $include (sort @{$implIncludeConditions{$condition}}) {
$contents .= "#include $include\n";
}
$contents .= "#endif\n";
}
$contents .= join "", @implContent;
$codeGenerator->UpdateFile($implFileName, $contents);
@implContentHeader = ();
@implContent = ();
%implIncludes = ();
# Update a .h file if the contents are changed.
$contents = join "", @headerContentHeader;
@includes = ();
foreach my $include (keys %headerIncludes) {
$include = "\"$include\"" unless $include =~ /^["<]/; # "
$include = SubstituteHeader($include);
push @includes, $include;
}
foreach my $include (sort @includes) {
# "JSClassName.h" is already included right after config.h.
next if $include eq "\"JS$name.h\"";
$contents .= "#include $include\n";
}
$contents .= join "", @headerContent;
@includes = ();
foreach my $include (keys %headerTrailingIncludes) {
$include = "\"$include\"" unless $include =~ /^["<]/; # "
push @includes, $include;
}
foreach my $include (sort @includes) {
$contents .= "#include $include\n";
}
$codeGenerator->UpdateFile($headerFileName, $contents);
@headerContentHeader = ();
@headerContent = ();
%headerIncludes = ();
%headerTrailingIncludes = ();
if (@depsContent) {
# Update a .dep file if the contents are changed.
$contents = join "", @depsContent;
$codeGenerator->UpdateFile($depsFileName, $contents);
@depsContent = ();
}
}
sub GeneratePrototypeDeclaration
{
my ($outputArray, $className, $interface) = @_;
return if $interface->isNamespaceObject;
my $prototypeClassName = "${className}Prototype";
my %structureFlags = ();
push(@$outputArray, "class ${prototypeClassName} final : public JSC::JSNonFinalObject {\n");
push(@$outputArray, "public:\n");
push(@$outputArray, " using Base = JSC::JSNonFinalObject;\n");
push(@$outputArray, " static ${prototypeClassName}* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure)\n");
push(@$outputArray, " {\n");
push(@$outputArray, " ${className}Prototype* ptr = new (NotNull, JSC::allocateCell<${className}Prototype>(vm.heap)) ${className}Prototype(vm, globalObject, structure);\n");
push(@$outputArray, " ptr->finishCreation(vm);\n");
push(@$outputArray, " return ptr;\n");
push(@$outputArray, " }\n\n");
push(@$outputArray, " DECLARE_INFO;\n");
push(@$outputArray, " template<typename CellType, JSC::SubspaceAccess>\n");
push(@$outputArray, " static JSC::IsoSubspace* subspaceFor(JSC::VM& vm)\n");
push(@$outputArray, " {\n");
push(@$outputArray, " STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(${prototypeClassName}, Base);\n");
push(@$outputArray, " return &vm.plainObjectSpace;\n");
push(@$outputArray, " }\n");
push(@$outputArray, " static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)\n");
push(@$outputArray, " {\n");
push(@$outputArray, " return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());\n");
push(@$outputArray, " }\n");
push(@$outputArray, "\nprivate:\n");
push(@$outputArray, " ${prototypeClassName}(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure)\n");
push(@$outputArray, " : JSC::JSNonFinalObject(vm, structure)\n");
push(@$outputArray, " {\n");
push(@$outputArray, " }\n");
push(@$outputArray, "\n");
push(@$outputArray, " void finishCreation(JSC::VM&);\n");
$structureFlags{"JSC::HasStaticPropertyTable"} = 1 if PrototypeHasStaticPropertyTable($interface) && IsGlobalInterface($interface);
$structureFlags{"JSC::IsImmutablePrototypeExoticObject"} = 1 if $interface->extendedAttributes->{IsImmutablePrototypeExoticObjectOnPrototype};
# structure flags
if (%structureFlags) {
push(@$outputArray, "public:\n");
push(@$outputArray, " static constexpr unsigned StructureFlags = Base::StructureFlags");
foreach my $structureFlag (sort (keys %structureFlags)) {
push(@$outputArray, " | " . $structureFlag);
}
push(@$outputArray, ";\n");
}
push(@$outputArray, "};\n");
push(@$outputArray, "STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(${prototypeClassName}, ${prototypeClassName}::Base);\n\n");
}
sub GetConstructorTemplateClassName
{
my $interface = shift;
return "JSDOMBuiltinConstructor" if HasJSBuiltinConstructor($interface);
return "JSDOMConstructorNotCallable" if $interface->isNamespaceObject;
return "JSDOMConstructorNotConstructable" if $interface->extendedAttributes->{LegacyFactoryFunction};
return "JSDOMConstructorNotConstructable" unless IsConstructable($interface);
return "JSDOMConstructor";
}
sub GenerateConstructorDeclaration
{
my ($outputArray, $className, $interface) = @_;
my $interfaceName = $interface->type->name;
my $constructorClassName = "${className}DOMConstructor";
my $templateClassName = GetConstructorTemplateClassName($interface);
AddToImplIncludes("${templateClassName}.h");
AddToImplIncludes("JSDOMLegacyFactoryFunction.h") if $interface->extendedAttributes->{LegacyFactoryFunction};
push(@$outputArray, "using $constructorClassName = $templateClassName<$className>;\n");
push(@$outputArray, "using JS${interfaceName}LegacyFactoryFunction = JSDOMLegacyFactoryFunction<$className>;\n") if $interface->extendedAttributes->{LegacyFactoryFunction};
push(@$outputArray, "\n");
}
sub GenerateConstructorDefinitions
{
my ($outputArray, $className, $protoClassName, $visibleInterfaceName, $interface, $generatingLegacyFactoryFunction) = @_;
if (IsConstructable($interface)) {
my @constructors = @{$interface->constructors};
if (@constructors > 1) {
foreach my $constructor (@constructors) {
GenerateConstructorDefinition($outputArray, $className, $protoClassName, $visibleInterfaceName, $interface, $generatingLegacyFactoryFunction, $constructor);
}
my $overloadFunctionPrefix = "construct${className}";
push(@implContent, "template<> EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ${className}DOMConstructor::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)\n");
push(@implContent, "{\n");
push(@implContent, " VM& vm = lexicalGlobalObject->vm();\n");
push(@implContent, " auto throwScope = DECLARE_THROW_SCOPE(vm);\n");
push(@implContent, " UNUSED_PARAM(throwScope);\n");
GenerateOverloadDispatcher(@{$interface->constructors}[0], $interface, $overloadFunctionPrefix, "", "lexicalGlobalObject, callFrame");
push(@implContent, "}\n");
push(@implContent, "JSC_ANNOTATE_HOST_FUNCTION(${className}ConstructorConstruct, ${className}DOMConstructor::construct);\n\n");
} elsif (@constructors == 1) {
GenerateConstructorDefinition($outputArray, $className, $protoClassName, $visibleInterfaceName, $interface, $generatingLegacyFactoryFunction, $constructors[0]);
} else {
GenerateConstructorDefinition($outputArray, $className, $protoClassName, $visibleInterfaceName, $interface, $generatingLegacyFactoryFunction);
}
}
GenerateConstructorHelperMethods($outputArray, $className, $protoClassName, $visibleInterfaceName, $interface, $generatingLegacyFactoryFunction);
}
sub GenerateConstructorDefinition
{
my ($outputArray, $className, $protoClassName, $visibleInterfaceName, $interface, $generatingLegacyFactoryFunction, $operation) = @_;
return if HasJSBuiltinConstructor($interface);
my $interfaceName = $interface->type->name;
my $constructorClassName = $generatingLegacyFactoryFunction ? "${className}LegacyFactoryFunction" : "${className}DOMConstructor";
if (IsConstructable($interface)) {
if (HasCustomConstructor($interface)) {
push(@$outputArray, "template<> JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ${constructorClassName}::construct(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame)\n");
push(@$outputArray, "{\n");
push(@$outputArray, " ASSERT(callFrame);\n");
push(@$outputArray, " return construct${className}(lexicalGlobalObject, *callFrame);\n");
push(@$outputArray, "}\n");
push(@implContent, "JSC_ANNOTATE_HOST_FUNCTION(${constructorClassName}Construct, ${constructorClassName}::construct);\n\n");
} elsif (!HasCustomConstructor($interface) && (!$interface->extendedAttributes->{LegacyFactoryFunction} || $generatingLegacyFactoryFunction)) {
my $isOverloaded = $operation->{overloads} && @{$operation->{overloads}} > 1;
if ($isOverloaded) {
push(@$outputArray, "static inline EncodedJSValue construct${className}$operation->{overloadIndex}(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)\n");
} else {
push(@$outputArray, "template<> EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ${constructorClassName}::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)\n");
}
push(@$outputArray, "{\n");
push(@$outputArray, " VM& vm = lexicalGlobalObject->vm();\n");
push(@$outputArray, " auto throwScope = DECLARE_THROW_SCOPE(vm);\n");
push(@$outputArray, " auto* castedThis = jsCast<${constructorClassName}*>(callFrame->jsCallee());\n");
push(@$outputArray, " ASSERT(castedThis);\n");
if ($operation->extendedAttributes->{EnabledBySetting}) {
my $runtimeEnableConditionalString = GenerateRuntimeEnableConditionalString($interface, $operation, "lexicalGlobalObject");
push(@$outputArray, " if (!${runtimeEnableConditionalString}) {\n");
push(@$outputArray, " throwTypeError(lexicalGlobalObject, throwScope, \"Illegal constructor\"_s);\n");
push(@$outputArray, " return JSValue::encode(jsNull());\n");
push(@$outputArray, " }\n");
}
GenerateArgumentsCountCheck($outputArray, $operation, $interface, " ");
my $functionImplementationName = $generatingLegacyFactoryFunction ? "createForLegacyFactoryFunction" : "create";
my $functionString = GenerateParametersCheck($outputArray, $operation, $interface, $functionImplementationName, " ");
push(@$outputArray, " auto object = ${functionString};\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, encodedJSValue());\n") if $codeGenerator->ExtendedAttributeContains($interface->extendedAttributes->{CallWith}, "ExecState");
push(@$outputArray, " static_assert(TypeOrExceptionOrUnderlyingType<decltype(object)>::isRef);\n");
my $IDLType = GetIDLType($interface, $interface->type);
my $implType = GetImplClassName($interface);
AddToImplIncludes("JSDOMConvertInterface.h");
my @constructionConversionArguments = ();
push(@constructionConversionArguments, "*lexicalGlobalObject");
push(@constructionConversionArguments, "*castedThis->globalObject()");
push(@constructionConversionArguments, "throwScope");
push(@constructionConversionArguments, "WTFMove(object)");
# FIXME: toJSNewlyCreated should return JSObject* instead of JSValue.
push(@$outputArray, " auto jsValue = toJSNewlyCreated<${IDLType}>(" . join(", ", @constructionConversionArguments) . ");\n");
push(@$outputArray, " if constexpr (IsExceptionOr<decltype(object)>)\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, { });\n");
push(@$outputArray, " setSubclassStructureIfNeeded<${implType}>(lexicalGlobalObject, callFrame, asObject(jsValue));\n");
push(@$outputArray, " RETURN_IF_EXCEPTION(throwScope, { });\n");
push(@$outputArray, " return JSValue::encode(jsValue);\n");
push(@$outputArray, "}\n");
if (!$isOverloaded) {
push(@$outputArray, "JSC_ANNOTATE_HOST_FUNCTION(${constructorClassName}Construct, ${constructorClassName}::construct);\n");
}
push(@$outputArray, "\n");
}
}
}
sub ConstructorHasProperties
{
my $interface = shift;
foreach my $constant (@{$interface->constants}) {
return 1;
}
foreach my $attribute (@{$interface->attributes}) {
next unless ($attribute->isStatic);
return 1;
}
foreach my $operation (@{$interface->operations}) {
next unless ($operation->isStatic);
return 1;
}
return 0;
}
sub GetRuntimeEnabledStaticProperties
{
my ($interface) = @_;
my @runtimeEnabledProperties = ();
foreach my $attribute (@{$interface->attributes}) {
next if AttributeShouldBeOnInstance($interface, $attribute) != 0;
next if not $attribute->isStatic;
if (NeedsRuntimeCheck($interface, $attribute)) {
push(@runtimeEnabledProperties, $attribute);
}
}
foreach my $constant (@{$interface->constants}) {
if (NeedsRuntimeCheck($interface, $constant)) {
push(@runtimeEnabledProperties, $constant);
}
}
foreach my $operation (@{$interface->operations}) {
next if ($operation->extendedAttributes->{PrivateIdentifier} and not $operation->extendedAttributes->{PublicIdentifier});
next if $operation->{overloadIndex} && $operation->{overloadIndex} > 1;
next if OperationShouldBeOnInstance($interface, $operation) != 0;
next if $operation->name eq "[Symbol.Iterator]";
next if not $operation->isStatic;
if (NeedsRuntimeCheck($interface, $operation)) {
push(@runtimeEnabledProperties, $operation);
}
}
return @runtimeEnabledProperties;
}
sub GenerateConstructorHelperMethods
{
my ($outputArray, $className, $protoClassName, $visibleInterfaceName, $interface, $generatingLegacyFactoryFunction) = @_;
my $constructorClassName = $generatingLegacyFactoryFunction ? "${className}LegacyFactoryFunction" : "${className}DOMConstructor";
my $leastConstructorLength = 0;
if (@{$interface->constructors} > 0) {
$leastConstructorLength = 255;
foreach my $constructor (@{$interface->constructors}) {
my $constructorLength = GetFunctionLength($constructor);
$leastConstructorLength = $constructorLength if ($constructorLength < $leastConstructorLength);
}
} else {
$leastConstructorLength = 0;
}
push(@$outputArray, "template<> const ClassInfo ${constructorClassName}::s_info = { \"${visibleInterfaceName}\", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(${constructorClassName}) };\n\n");
# If the interface has a parent interface which does not have [LegacyNoInterfaceObject], then use its interface object as prototype,
# otherwise use FunctionPrototype: http://heycam.github.io/webidl/#interface-object
push(@$outputArray, "template<> JSValue ${constructorClassName}::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject)\n");
push(@$outputArray, "{\n");
assert("An interface cannot inherit from another interface that is marked as [LegacyNoInterfaceObject]") if $interface->parentType && $codeGenerator->GetInterfaceExtendedAttributesFromName($interface->parentType->name)->{LegacyNoInterfaceObject};
if (!$generatingLegacyFactoryFunction and $interface->parentType) {
my $parentClassName = "JS" . $interface->parentType->name;
push(@$outputArray, " return ${parentClassName}::getConstructor(vm, &globalObject);\n");
} elsif ($interface->isNamespaceObject) {
AddToImplIncludes("<JavaScriptCore/ObjectPrototype.h>");
push(@$outputArray, " UNUSED_PARAM(vm);\n");
push(@$outputArray, " return globalObject.objectPrototype();\n");
} else {
AddToImplIncludes("<JavaScriptCore/FunctionPrototype.h>");
push(@$outputArray, " UNUSED_PARAM(vm);\n");
push(@$outputArray, " return globalObject.functionPrototype();\n");
}
push(@$outputArray, "}\n\n");
push(@$outputArray, "template<> void ${constructorClassName}::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject)\n");
push(@$outputArray, "{\n");
# There must exist an interface prototype object for every non-callback interface defined, regardless
# of whether the interface was declared with the [LegacyNoInterfaceObject] extended attribute.
# https://heycam.github.io/webidl/#interface-prototype-object
if (ShouldUseGlobalObjectPrototype($interface)) {
push(@$outputArray, " putDirect(vm, vm.propertyNames->prototype, globalObject.getPrototypeDirect(vm), JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);\n");
} elsif ($interface->isCallback) {
push(@$outputArray, " UNUSED_PARAM(globalObject);\n");
} elsif ($interface->isNamespaceObject) {
push(@$outputArray, " JSC_TO_STRING_TAG_WITHOUT_TRANSITION();\n");
} else {
push(@$outputArray, " putDirect(vm, vm.propertyNames->prototype, ${className}::prototype(vm, globalObject), JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);\n");
}
assert("jsNontrivialString() requires strings two or more characters long") if length($visibleInterfaceName) < 2;
push(@$outputArray, " putDirect(vm, vm.propertyNames->name, jsNontrivialString(vm, \"$visibleInterfaceName\"_s), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);\n") if !$interface->isNamespaceObject;
if ($interface->extendedAttributes->{LegacyFactoryFunctionEnabledBySetting}) {
my $runtimeEnableConditionalString = GenerateRuntimeEnableConditionalString($interface, $interface, "&globalObject");
push(@$outputArray, " int constructorLength = ${leastConstructorLength};\n");
push(@$outputArray, " if (!${runtimeEnableConditionalString})\n");
push(@$outputArray, " constructorLength = 0;\n");
push(@$outputArray, " putDirect(vm, vm.propertyNames->length, jsNumber(constructorLength), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);\n");
} elsif (!$interface->isNamespaceObject) {
push(@$outputArray, " putDirect(vm, vm.propertyNames->length, jsNumber(${leastConstructorLength}), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);\n");
}
my $classForThis = "${className}::info()";
if ($interface->isCallback) {
$classForThis = "nullptr";
}
push(@$outputArray, " reifyStaticProperties(vm, ${classForThis}, ${className}ConstructorTableValues, *this);\n") if ConstructorHasProperties($interface);
my @runtimeEnabledProperties = GetRuntimeEnabledStaticProperties($interface);
foreach my $operationOrAttribute (@runtimeEnabledProperties) {
my $conditionalString = $codeGenerator->GenerateConditionalString($operationOrAttribute);
push(@$outputArray, "#if ${conditionalString}\n") if $conditionalString;
my $runtimeEnableConditionalString = GenerateRuntimeEnableConditionalString($interface, $operationOrAttribute, "&globalObject");
my $name = $operationOrAttribute->name;
push(@$outputArray, " if (!${runtimeEnableConditionalString}) {\n");
push(@$outputArray, " auto propertyName = Identifier::fromString(vm, reinterpret_cast<const LChar*>(\"$name\"), strlen(\"$name\"));\n");
push(@$outputArray, " VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);\n");
push(@$outputArray, " DeletePropertySlot slot;\n");
push(@$outputArray, " JSObject::deleteProperty(this, &globalObject, propertyName, slot);\n");
push(@$outputArray, " }\n");
push(@$outputArray, "#endif\n") if $conditionalString;
}
push(@$outputArray, "}\n\n");
if (HasJSBuiltinConstructor($interface)) {
push(@$outputArray, "template<> FunctionExecutable* ${constructorClassName}::initializeExecutable(VM& vm)\n");
push(@$outputArray, "{\n");
push(@$outputArray, " return " . GetJSBuiltinFunctionNameFromString($interface->type->name, "initialize" . $interface->type->name) . "(vm);\n");
push(@$outputArray, "}\n");
push(@$outputArray, "\n");
}
}
sub HasCustomConstructor
{
my ($interface) = @_;
my $hasCustomConstuctor = 0;
my $hasNonCustomConstuctor = 0;
foreach my $constructor (@{$interface->constructors}) {
if ($constructor->extendedAttributes->{Custom}) {
$hasCustomConstuctor = 1;
} else {
$hasNonCustomConstuctor = 1;
}
}
assert("Using both Custom and non-Custom constructors on the same interface is not supported at this time") if $hasCustomConstuctor && $hasNonCustomConstuctor;
return $hasCustomConstuctor;
}
sub HasCustomGetter
{
my $attribute = shift;
return $attribute->extendedAttributes->{Custom} || $attribute->extendedAttributes->{CustomGetter};
}
sub HasCustomSetter
{
my $attribute = shift;
return $attribute->extendedAttributes->{Custom} || $attribute->extendedAttributes->{CustomSetter};
}
sub HasCustomMethod
{
my $operation = shift;
return $operation->extendedAttributes->{Custom};
}
sub NeedsConstructorProperty
{
my $interface = shift;
return 0 if $interface->isNamespaceObject;
return !$interface->extendedAttributes->{LegacyNoInterfaceObject};
}
sub IsConstructable
{
my $interface = shift;
return @{$interface->constructors} > 0;
}
sub InstanceOverridesGetCallData
{
my $interface = shift;
return $interface->extendedAttributes->{CustomGetCallData} || $interface->extendedAttributes->{Plugin};
}
sub HeaderNeedsPrototypeDeclaration
{
my $interface = shift;
return IsDOMGlobalObject($interface);
}
sub IsLegacyUnforgeable
{
my ($interface, $property) = @_;
return $property->extendedAttributes->{LegacyUnforgeable} || $interface->extendedAttributes->{LegacyUnforgeable};
}
sub ComputeFunctionSpecial
{
my ($interface, $operation) = @_;
my @specials = ();
push(@specials, ("JSC::PropertyAttribute::DontDelete", "JSC::PropertyAttribute::ReadOnly")) if IsLegacyUnforgeable($interface, $operation);
push(@specials, "JSC::PropertyAttribute::DontEnum") if $operation->extendedAttributes->{NotEnumerable};
if (IsJSBuiltin($interface, $operation)) {
push(@specials, "JSC::PropertyAttribute::Builtin");
} else {
push(@specials, "JSC::PropertyAttribute::Function");
}
if ($operation->extendedAttributes->{DOMJIT}) {
push(@specials, "JSC::PropertyAttribute::DOMJITFunction") if $operation->extendedAttributes->{DOMJIT};
}
return "static_cast<unsigned>(" . ((@specials > 0) ? join(" | ", @specials) : "0") . ")";
}
sub IsJSBuiltin
{
my ($interface, $object) = @_;
return 0 if $object->extendedAttributes->{Custom};
return 0 if $object->extendedAttributes->{CustomGetter};
return 0 if $object->extendedAttributes->{CustomSetter};
return 1 if $object->extendedAttributes->{JSBuiltin};
return 1 if $interface->extendedAttributes->{JSBuiltin};
return 0;
}
sub HasJSBuiltinConstructor
{
my ($interface) = @_;
return 1 if $interface->extendedAttributes->{JSBuiltin};
my $hasJSBuiltinConstuctor = 0;
my $hasNonJSBuiltinConstuctor = 0;
foreach my $constructor (@{$interface->constructors}) {
if ($constructor->extendedAttributes->{JSBuiltin}) {
$hasJSBuiltinConstuctor = 1;
} else {
$hasNonJSBuiltinConstuctor = 1;
}
}
assert("Using both JSBuiltin and non-JSBuiltin constructors on the same interface is not supported at this time") if $hasJSBuiltinConstuctor && $hasNonJSBuiltinConstuctor;
return $hasJSBuiltinConstuctor;
}
sub GetJSBuiltinFunctionName
{
my ($className, $operation) = @_;
my $scopeName = $operation->extendedAttributes->{ImplementedBy};
$scopeName = substr $className, 2 unless $scopeName;
return GetJSBuiltinFunctionNameFromString($scopeName, $operation->name);
}
sub GetJSBuiltinFunctionNameFromString
{
my ($scopeName, $functionName) = @_;
return $codeGenerator->WK_lcfirst($scopeName) . $codeGenerator->WK_ucfirst($functionName) . "CodeGenerator";
}
sub GetJSBuiltinScopeName
{
my ($interface, $object) = @_;
return $object->extendedAttributes->{ImplementedBy} || $interface->type->name;
}
sub AddJSBuiltinIncludesIfNeeded()
{
my $interface = shift;
if ($interface->extendedAttributes->{JSBuiltin} || HasJSBuiltinConstructor($interface)) {
AddToImplIncludes($interface->type->name . "Builtins.h");
return;
}
foreach my $operation (@{$interface->operations}) {
AddToImplIncludes(GetJSBuiltinScopeName($interface, $operation) . "Builtins.h", $operation->extendedAttributes->{Conditional}) if IsJSBuiltin($interface, $operation);
}
foreach my $attribute (@{$interface->attributes}) {
AddToImplIncludes(GetJSBuiltinScopeName($interface, $attribute) . "Builtins.h", $attribute->extendedAttributes->{Conditional}) if IsJSBuiltin($interface, $attribute);
}
}
sub GenerateCallTracer()
{
my ($outputArray, $callTracer, $name, $arguments, $indent) = @_;
AddToImplIncludes($callTracer . ".h");
push(@$outputArray, $indent . "if (UNLIKELY(impl.hasActive" . $callTracer . "()))\n");
push(@$outputArray, $indent . " " . $callTracer . "::recordAction(impl, \"" . $name . "\"_s");
if (scalar(@$arguments)) {
push(@$outputArray, ", { " . join(", ", map { $callTracer . "::processArgument(impl, " . $_ . ")" } @$arguments) . " }");
}
push(@$outputArray, ");\n");
}
sub GenerateCustomElementReactionsStackIfNeeded
{
my ($outputArray, $context, $stateVariable) = @_;
my $CEReactions = $context->extendedAttributes->{CEReactions};
return if !$CEReactions;
AddToImplIncludes("CustomElementReactionQueue.h");
if ($CEReactions eq "NotNeeded") {
push(@$outputArray, " CustomElementReactionDisallowedScope customElementReactionDisallowedScope;\n");
} else {
push(@$outputArray, " CustomElementReactionStack customElementReactionStack($stateVariable);\n");
}
}
sub MakeSharedSyntheticAttribute
{
my ($name, $baseAttribute) = @_;
my $sharedAttribute = IDLAttribute->new();
$sharedAttribute->name($name);
$sharedAttribute->type(IDLParser::cloneType($baseAttribute->type));
IDLParser::copyExtendedAttributes($sharedAttribute->extendedAttributes, $baseAttribute->extendedAttributes);
# Replace "DelegateToSharedSyntheticAttribute" extended attribute with "IsSharedSyntheticAttribute" extended attribute to
# that this is the real synthesized attribute.
delete $sharedAttribute->extendedAttributes->{DelegateToSharedSyntheticAttribute};
$sharedAttribute->extendedAttributes->{IsSharedSyntheticAttribute} = 1;
return $sharedAttribute;
}
sub AddSharedSyntheticAttributesIfNeeded
{
my $interface = shift;
# Use a fake extended attribute, SyntheticIDLAttributes, to store the new attributes
if (not exists $interface->extendedAttributes->{SyntheticIDLAttributes}) {
my %syntheticIDLAttributes = ( );
my @attributes = ();
foreach my $attribute (@{$interface->attributes}) {
if ($attribute->extendedAttributes->{DelegateToSharedSyntheticAttribute}) {
my $name = $attribute->extendedAttributes->{DelegateToSharedSyntheticAttribute};
if (not $syntheticIDLAttributes{$name}) {
my $syntheticAttribute = MakeSharedSyntheticAttribute($name, $attribute);
$syntheticIDLAttributes{$name} = 1;
push(@attributes, $syntheticAttribute);
}
}
}
$interface->extendedAttributes->{SyntheticIDLAttributes} = \@attributes;
}
}
sub GetSharedSyntheticAttribute
{
my ($interface, $attribute) = @_;
foreach my $syntheticAttribute (@{$interface->extendedAttributes->{SyntheticIDLAttributes}}) {
if ($attribute->extendedAttributes->{DelegateToSharedSyntheticAttribute} eq $syntheticAttribute->name()) {
return $syntheticAttribute;
}
}
return undef;
}
1;