blob: e7cbd50734874ace06a362adf64e19502911b184 [file] [log] [blame]
# Copyright (C) 2013 Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Functions shared by various parts of the code generator.
Design doc: http://www.chromium.org/developers/design-documents/idl-compiler
"""
import re
from idl_types import IdlTypeBase
import idl_types
from idl_definitions import Exposure, IdlInterface, IdlAttribute
from v8_globals import includes
ACRONYMS = [
'CSSOM', # must come *before* CSS to match full acronym
'CSS',
'HTML',
'IME',
'JS',
'SVG',
'URL',
'WOFF',
'XML',
'XSLT',
]
################################################################################
# Extended attribute parsing
################################################################################
def extended_attribute_value_contains(extended_attribute_value, key):
return (extended_attribute_value == key or
(isinstance(extended_attribute_value, list) and
key in extended_attribute_value))
def has_extended_attribute(definition_or_member, extended_attribute_list):
return any(extended_attribute in definition_or_member.extended_attributes
for extended_attribute in extended_attribute_list)
def has_extended_attribute_value(definition_or_member, name, value):
extended_attributes = definition_or_member.extended_attributes
return (name in extended_attributes and
extended_attribute_value_contains(extended_attributes[name], value))
def extended_attribute_value_as_list(definition_or_member, name):
extended_attributes = definition_or_member.extended_attributes
if name not in extended_attributes:
return None
value = extended_attributes[name]
if isinstance(value, list):
return value
return [value]
################################################################################
# String handling
################################################################################
def capitalize(name):
"""Capitalize first letter or initial acronym (used in setter names)."""
for acronym in ACRONYMS:
if name.startswith(acronym.lower()):
return name.replace(acronym.lower(), acronym)
return name[0].upper() + name[1:]
def strip_suffix(string, suffix):
if not suffix or not string.endswith(suffix):
return string
return string[:-len(suffix)]
def uncapitalize(name):
"""Uncapitalizes first letter or initial acronym (used in method names).
E.g., 'SetURL' becomes 'setURL', but 'URLFoo' becomes 'urlFoo'.
"""
for acronym in ACRONYMS:
if name.startswith(acronym):
return name.replace(acronym, acronym.lower())
return name[0].lower() + name[1:]
def unique_by(dict_list, key):
"""Returns elements from a list of dictionaries with unique values for the named key."""
keys_seen = set()
filtered_list = []
for item in dict_list:
if item.get(key) not in keys_seen:
filtered_list.append(item)
keys_seen.add(item.get(key))
return filtered_list
def for_origin_trial_feature(items, feature_name):
"""Filters the list of attributes or constants, and returns those defined for the named origin trial feature."""
return [item for item in items if
item['origin_trial_feature_name'] == feature_name and
not item.get('exposed_test')]
################################################################################
# C++
################################################################################
def scoped_name(interface, definition, base_name):
if 'ImplementedInPrivateScript' in definition.extended_attributes:
return '%s::PrivateScript::%s' % (v8_class_name(interface), base_name)
# partial interfaces are implemented as separate classes, with their members
# implemented as static member functions
partial_interface_implemented_as = definition.extended_attributes.get('PartialInterfaceImplementedAs')
if partial_interface_implemented_as:
return '%s::%s' % (partial_interface_implemented_as, base_name)
if (definition.is_static or
definition.name in ('Constructor', 'NamedConstructor')):
return '%s::%s' % (cpp_name(interface), base_name)
return 'impl->%s' % base_name
def v8_class_name(interface):
return 'V8' + interface.name
def v8_class_name_or_partial(interface):
class_name = v8_class_name(interface)
if interface.is_partial:
return ''.join([class_name, 'Partial'])
return class_name
################################################################################
# Specific extended attributes
################################################################################
# [ActivityLogging]
def activity_logging_world_list(member, access_type=''):
"""Returns a set of world suffixes for which a definition member has activity logging, for specified access type.
access_type can be 'Getter' or 'Setter' if only checking getting or setting.
"""
extended_attributes = member.extended_attributes
if 'LogActivity' not in extended_attributes:
return set()
log_activity = extended_attributes['LogActivity']
if log_activity and not log_activity.startswith(access_type):
return set()
includes.add('bindings/core/v8/V8DOMActivityLogger.h')
if 'LogAllWorlds' in extended_attributes:
return set(['', 'ForMainWorld'])
return set(['']) # At minimum, include isolated worlds.
# [ActivityLogging]
def activity_logging_world_check(member):
"""Returns if an isolated world check is required when generating activity
logging code.
The check is required when there is no per-world binding code and logging is
required only for isolated world.
"""
extended_attributes = member.extended_attributes
if 'LogActivity' not in extended_attributes:
return False
if ('PerWorldBindings' not in extended_attributes and
'LogAllWorlds' not in extended_attributes):
return True
return False
# [CallWith]
CALL_WITH_ARGUMENTS = {
'ScriptState': 'scriptState',
'ExecutionContext': 'executionContext',
'ScriptArguments': 'scriptArguments',
'CurrentWindow': 'currentDOMWindow(info.GetIsolate())',
'EnteredWindow': 'enteredDOMWindow(info.GetIsolate())',
'Document': 'document',
'ThisValue': 'ScriptValue(scriptState, info.Holder())',
}
# List because key order matters, as we want arguments in deterministic order
CALL_WITH_VALUES = [
'ScriptState',
'ExecutionContext',
'ScriptArguments',
'CurrentWindow',
'EnteredWindow',
'Document',
'ThisValue',
]
def call_with_arguments(call_with_values):
if not call_with_values:
return []
return [CALL_WITH_ARGUMENTS[value]
for value in CALL_WITH_VALUES
if extended_attribute_value_contains(call_with_values, value)]
# [Constructor], [NamedConstructor]
def is_constructor_attribute(member):
# TODO(yukishiino): replace this with [Constructor] and [NamedConstructor] extended attribute
return (type(member) == IdlAttribute and
member.idl_type.name.endswith('Constructor'))
# [DeprecateAs]
def deprecate_as(member):
extended_attributes = member.extended_attributes
if 'DeprecateAs' not in extended_attributes:
return None
includes.add('core/frame/Deprecation.h')
return extended_attributes['DeprecateAs']
# [Exposed]
EXPOSED_EXECUTION_CONTEXT_METHOD = {
'AnimationWorklet': 'isAnimationWorkletGlobalScope',
'CompositorWorker': 'isCompositorWorkerGlobalScope',
'DedicatedWorker': 'isDedicatedWorkerGlobalScope',
'PaintWorklet': 'isPaintWorkletGlobalScope',
'ServiceWorker': 'isServiceWorkerGlobalScope',
'SharedWorker': 'isSharedWorkerGlobalScope',
'Window': 'isDocument',
'Worker': 'isWorkerGlobalScope',
'Worklet': 'isWorkletGlobalScope',
}
EXPOSED_WORKERS = set([
'CompositorWorker',
'DedicatedWorker',
'SharedWorker',
'ServiceWorker',
])
class ExposureSet:
"""An ExposureSet is a collection of Exposure instructions."""
def __init__(self, exposures=None):
self.exposures = set(exposures) if exposures else set()
def issubset(self, other):
"""Returns true if |self|'s exposure set is a subset of
|other|'s exposure set. This function doesn't care about
RuntimeEnabled."""
self_set = self._extended(set(e.exposed for e in self.exposures))
other_set = self._extended(set(e.exposed for e in other.exposures))
return self_set.issubset(other_set)
@staticmethod
def _extended(target):
if EXPOSED_WORKERS.issubset(target):
return target | set(['Worker'])
elif 'Worker' in target:
return target | EXPOSED_WORKERS
return target
def add(self, exposure):
self.exposures.add(exposure)
def __len__(self):
return len(self.exposures)
def __iter__(self):
return self.exposures.__iter__()
@staticmethod
def _code(exposure):
exposed = ('executionContext->%s()' %
EXPOSED_EXECUTION_CONTEXT_METHOD[exposure.exposed])
if exposure.runtime_enabled is not None:
runtime_enabled = ('RuntimeEnabledFeatures::%sEnabled()' %
uncapitalize(exposure.runtime_enabled))
return '({0} && {1})'.format(exposed, runtime_enabled)
return exposed
def code(self):
if len(self.exposures) == 0:
return None
# We use sorted here to deflake output.
return ' || '.join(sorted(self._code(e) for e in self.exposures))
def exposed(member, interface):
"""Returns a C++ code that checks if a method/attribute/etc is exposed.
When the Exposed attribute contains RuntimeEnabledFeatures (i.e.
Exposed(Arguments) form is given), the code contains check for them as
well.
EXAMPLE: [Exposed=Window, RuntimeEnabledFeature=Feature1]
=> context->isDocument()
EXAMPLE: [Exposed(Window Feature1, Window Feature2)]
=> context->isDocument() && RuntimeEnabledFeatures::feature1Enabled() ||
context->isDocument() && RuntimeEnabledFeatures::feature2Enabled()
"""
exposure_set = ExposureSet(
extended_attribute_value_as_list(member, 'Exposed'))
interface_exposure_set = ExposureSet(
extended_attribute_value_as_list(interface, 'Exposed'))
for e in exposure_set:
if e.exposed not in EXPOSED_EXECUTION_CONTEXT_METHOD:
raise ValueError('Invalid execution context: %s' % e.exposed)
# Methods must not be exposed to a broader scope than their interface.
if not exposure_set.issubset(interface_exposure_set):
raise ValueError('Interface members\' exposure sets must be a subset of the interface\'s.')
return exposure_set.code()
# [SecureContext]
def secure_context(member, interface):
"""Returns C++ code that checks whether an interface/method/attribute/etc. is exposed
to the current context."""
if 'SecureContext' in member.extended_attributes or 'SecureContext' in interface.extended_attributes:
return "executionContext->isSecureContext()"
return None
# [ImplementedAs]
def cpp_name(definition_or_member):
extended_attributes = definition_or_member.extended_attributes
if 'ImplementedAs' not in extended_attributes:
return definition_or_member.name
return extended_attributes['ImplementedAs']
def cpp_name_from_interfaces_info(name, interfaces_info):
return interfaces_info.get(name, {}).get('implemented_as') or name
def cpp_name_or_partial(interface):
cpp_class_name = cpp_name(interface)
if interface.is_partial:
return ''.join([cpp_class_name, 'Partial'])
return cpp_class_name
# [MeasureAs]
def measure_as(definition_or_member, interface):
extended_attributes = definition_or_member.extended_attributes
if 'MeasureAs' in extended_attributes:
includes.add('core/frame/UseCounter.h')
return lambda suffix: extended_attributes['MeasureAs']
if 'Measure' in extended_attributes:
includes.add('core/frame/UseCounter.h')
measure_as_name = capitalize(definition_or_member.name)
if interface is not None:
measure_as_name = '%s_%s' % (capitalize(interface.name), measure_as_name)
return lambda suffix: 'V8%s_%s' % (measure_as_name, suffix)
return None
# [OriginTrialEnabled]
def origin_trial_enabled_function_name(definition_or_member):
"""Returns the name of the OriginTrials enabled function.
An exception is raised if both the OriginTrialEnabled and RuntimeEnabled
extended attributes are applied to the same IDL member. Only one of the
two attributes can be applied to any member - they are mutually exclusive.
The returned function checks if the IDL member should be enabled.
Given extended attribute OriginTrialEnabled=FeatureName, return:
OriginTrials::{featureName}Enabled
If the OriginTrialEnabled extended attribute is found, the includes are
also updated as a side-effect.
"""
extended_attributes = definition_or_member.extended_attributes
is_origin_trial_enabled = 'OriginTrialEnabled' in extended_attributes
if is_origin_trial_enabled and 'RuntimeEnabled' in extended_attributes:
raise Exception('[OriginTrialEnabled] and [RuntimeEnabled] must '
'not be specified on the same definition: '
'%s.%s' % (definition_or_member.idl_name, definition_or_member.name))
if is_origin_trial_enabled:
trial_name = extended_attributes['OriginTrialEnabled']
return 'OriginTrials::%sEnabled' % uncapitalize(trial_name)
is_feature_policy_enabled = 'FeaturePolicy' in extended_attributes
if is_feature_policy_enabled and 'RuntimeEnabled' in extended_attributes:
raise Exception('[FeaturePolicy] and [RuntimeEnabled] must '
'not be specified on the same definition: '
'%s.%s' % (definition_or_member.idl_name, definition_or_member.name))
if is_feature_policy_enabled:
includes.add('bindings/core/v8/ScriptState.h')
includes.add('platform/feature_policy/FeaturePolicy.h')
trial_name = extended_attributes['FeaturePolicy']
return 'FeaturePolicy::%sEnabled' % uncapitalize(trial_name)
return None
def origin_trial_feature_name(definition_or_member):
extended_attributes = definition_or_member.extended_attributes
return extended_attributes.get('OriginTrialEnabled') or extended_attributes.get('FeaturePolicy')
def runtime_feature_name(definition_or_member):
extended_attributes = definition_or_member.extended_attributes
if 'RuntimeEnabled' not in extended_attributes:
return None
return extended_attributes['RuntimeEnabled']
# [RuntimeEnabled]
def runtime_enabled_function_name(definition_or_member):
"""Returns the name of the RuntimeEnabledFeatures function.
The returned function checks if a method/attribute is enabled.
Given extended attribute RuntimeEnabled=FeatureName, return:
RuntimeEnabledFeatures::{featureName}Enabled
If the RuntimeEnabled extended attribute is found, the includes
are also updated as a side-effect.
"""
feature_name = runtime_feature_name(definition_or_member)
if not feature_name:
return
includes.add('platform/RuntimeEnabledFeatures.h')
return 'RuntimeEnabledFeatures::%sEnabled' % uncapitalize(feature_name)
# [Unforgeable]
def is_unforgeable(interface, member):
return (('Unforgeable' in interface.extended_attributes or
'Unforgeable' in member.extended_attributes) and
not member.is_static)
# [LegacyInterfaceTypeChecking]
def is_legacy_interface_type_checking(interface, member):
return ('LegacyInterfaceTypeChecking' in interface.extended_attributes or
'LegacyInterfaceTypeChecking' in member.extended_attributes)
# [Unforgeable], [Global], [PrimaryGlobal]
def on_instance(interface, member):
"""Returns True if the interface's member needs to be defined on every
instance object.
The following members must be defiend on an instance object.
- [Unforgeable] members
- regular members of [Global] or [PrimaryGlobal] interfaces
"""
if member.is_static:
return False
# TODO(yukishiino): Remove a hack for toString once we support
# Symbol.toStringTag.
if (interface.name == 'Window' and member.name == 'toString'):
return False
# TODO(yukishiino): Implement "interface object" and its [[Call]] method
# in a better way. Then we can get rid of this hack.
if is_constructor_attribute(member):
return True
if ('PrimaryGlobal' in interface.extended_attributes or
'Global' in interface.extended_attributes or
'Unforgeable' in member.extended_attributes or
'Unforgeable' in interface.extended_attributes):
return True
return False
def on_prototype(interface, member):
"""Returns True if the interface's member needs to be defined on the
prototype object.
Most members are defined on the prototype object. Exceptions are as
follows.
- static members (optional)
- [Unforgeable] members
- members of [Global] or [PrimaryGlobal] interfaces
- named properties of [Global] or [PrimaryGlobal] interfaces
"""
if member.is_static:
return False
# TODO(yukishiino): Remove a hack for toString once we support
# Symbol.toStringTag.
if (interface.name == 'Window' and member.name == 'toString'):
return True
# TODO(yukishiino): Implement "interface object" and its [[Call]] method
# in a better way. Then we can get rid of this hack.
if is_constructor_attribute(member):
return False
if ('PrimaryGlobal' in interface.extended_attributes or
'Global' in interface.extended_attributes or
'Unforgeable' in member.extended_attributes or
'Unforgeable' in interface.extended_attributes):
return False
return True
# static, const
def on_interface(interface, member):
"""Returns True if the interface's member needs to be defined on the
interface object.
The following members must be defiend on an interface object.
- static members
"""
if member.is_static:
return True
return False
################################################################################
# Indexed properties
# http://heycam.github.io/webidl/#idl-indexed-properties
################################################################################
def indexed_property_getter(interface):
try:
# Find indexed property getter, if present; has form:
# getter TYPE [OPTIONAL_IDENTIFIER](unsigned long ARG1)
return next(
method
for method in interface.operations
if ('getter' in method.specials and
len(method.arguments) == 1 and
str(method.arguments[0].idl_type) == 'unsigned long'))
except StopIteration:
return None
def indexed_property_setter(interface):
try:
# Find indexed property setter, if present; has form:
# setter RETURN_TYPE [OPTIONAL_IDENTIFIER](unsigned long ARG1, ARG_TYPE ARG2)
return next(
method
for method in interface.operations
if ('setter' in method.specials and
len(method.arguments) == 2 and
str(method.arguments[0].idl_type) == 'unsigned long'))
except StopIteration:
return None
def indexed_property_deleter(interface):
try:
# Find indexed property deleter, if present; has form:
# deleter TYPE [OPTIONAL_IDENTIFIER](unsigned long ARG)
return next(
method
for method in interface.operations
if ('deleter' in method.specials and
len(method.arguments) == 1 and
str(method.arguments[0].idl_type) == 'unsigned long'))
except StopIteration:
return None
################################################################################
# Named properties
# http://heycam.github.io/webidl/#idl-named-properties
################################################################################
def named_property_getter(interface):
try:
# Find named property getter, if present; has form:
# getter TYPE [OPTIONAL_IDENTIFIER](DOMString ARG1)
getter = next(
method
for method in interface.operations
if ('getter' in method.specials and
len(method.arguments) == 1 and
str(method.arguments[0].idl_type) == 'DOMString'))
getter.name = getter.name or 'anonymousNamedGetter'
return getter
except StopIteration:
return None
def named_property_setter(interface):
try:
# Find named property setter, if present; has form:
# setter RETURN_TYPE [OPTIONAL_IDENTIFIER](DOMString ARG1, ARG_TYPE ARG2)
return next(
method
for method in interface.operations
if ('setter' in method.specials and
len(method.arguments) == 2 and
str(method.arguments[0].idl_type) == 'DOMString'))
except StopIteration:
return None
def named_property_deleter(interface):
try:
# Find named property deleter, if present; has form:
# deleter TYPE [OPTIONAL_IDENTIFIER](DOMString ARG)
return next(
method
for method in interface.operations
if ('deleter' in method.specials and
len(method.arguments) == 1 and
str(method.arguments[0].idl_type) == 'DOMString'))
except StopIteration:
return None
IdlInterface.indexed_property_getter = property(indexed_property_getter)
IdlInterface.indexed_property_setter = property(indexed_property_setter)
IdlInterface.indexed_property_deleter = property(indexed_property_deleter)
IdlInterface.named_property_getter = property(named_property_getter)
IdlInterface.named_property_setter = property(named_property_setter)
IdlInterface.named_property_deleter = property(named_property_deleter)