Index: third_party/WebKit/Source/bindings/scripts/generate_conditional_features.py |
diff --git a/third_party/WebKit/Source/bindings/scripts/generate_conditional_features.py b/third_party/WebKit/Source/bindings/scripts/generate_conditional_features.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..861e2c25f1598026c8ef806f2c93168167767380 |
--- /dev/null |
+++ b/third_party/WebKit/Source/bindings/scripts/generate_conditional_features.py |
@@ -0,0 +1,232 @@ |
+#!/usr/bin/python |
+# |
+# Copyright 2017 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+# This script reads the global interface data collected by |
+# compute_interfaces_info_overall.py, and writes out the code which adds |
+# bindings for origin-trial-enabled features at runtime. |
+ |
+# pylint: disable=W0403 |
+ |
+import optparse |
+import os |
+import posixpath |
+import sys |
+from collections import defaultdict, namedtuple |
+ |
+from code_generator import (initialize_jinja_env, normalize_and_sort_includes, |
+ render_template) |
+from idl_reader import IdlReader |
+from utilities import (create_component_info_provider, write_file, |
+ idl_filename_to_component) |
+from v8_utilities import v8_class_name, v8_class_name_or_partial, uncapitalize |
+ |
+# Make sure extension is .py, not .pyc or .pyo, so doesn't depend on caching |
+MODULE_PYNAME = os.path.splitext(os.path.basename(__file__))[0] + '.py' |
+ |
+ |
+ConditionalInterfaceInfo = namedtuple('ConditionalInterfaceInfo', [ |
+ 'name', 'v8_class', 'v8_class_or_partial', 'is_global']) |
+ |
+ |
+def get_install_functions(interfaces, feature_names): |
+ """Construct a list of V8 bindings installation functions for each feature |
+ on each interface. |
+ |
+ interfaces is a list of ConditionalInterfaceInfo tuples |
+ feature_names is a list of strings, containing names of features which can |
+ be installed on those interfaces. |
+ """ |
+ return [ |
+ {"condition": 'OriginTrials::%sEnabled' % uncapitalize(feature_name), |
+ "name": feature_name, |
+ "install_method": "install%s" % feature_name, |
+ "v8_class": interface_info.v8_class, |
+ "v8_class_or_partial": interface_info.v8_class_or_partial} |
+ for feature_name in feature_names |
+ for interface_info in interfaces] |
+ |
+ |
+def get_conditional_feature_names_from_interface(interface): |
+ feature_names = set() |
+ if ('OriginTrialEnabled' in interface.extended_attributes and |
+ interface.is_partial): |
+ feature_names.add(interface.extended_attributes['OriginTrialEnabled']) |
+ for operation in interface.operations: |
+ if 'OriginTrialEnabled' in operation.extended_attributes: |
+ feature_names.add( |
+ operation.extended_attributes['OriginTrialEnabled']) |
+ for attribute in interface.attributes: |
+ if 'OriginTrialEnabled' in attribute.extended_attributes: |
+ feature_names.add( |
+ attribute.extended_attributes['OriginTrialEnabled']) |
+ return list(feature_names) |
+ |
+ |
+def read_idl_file(reader, idl_filename): |
+ interfaces = reader.read_idl_file(idl_filename).interfaces |
+ # There should only be a single interface defined in an IDL file. Return it. |
+ assert len(interfaces) == 1 |
+ return interfaces.values()[0] |
+ |
+ |
+def interface_is_global(interface): |
+ return ('Global' in interface.extended_attributes or |
+ 'PrimaryGlobal' in interface.extended_attributes) |
+ |
+ |
+def conditional_features_info(info_provider, reader, idl_filenames, target_component): |
+ """Read a set of IDL files and compile the mapping between interfaces and |
+ the conditional features defined on them. |
+ |
+ Returns a tuple (features_for_type, types_for_feature, includes): |
+ - features_for_type is a mapping of interface->feature |
+ - types_for_feature is the reverse mapping: feature->interface |
+ - includes is a set of header files which need to be included in the |
+ generated implementation code. |
+ """ |
+ features_for_type = defaultdict(set) |
+ types_for_feature = defaultdict(set) |
+ includes = set() |
+ |
+ for idl_filename in idl_filenames: |
+ interface = read_idl_file(reader, idl_filename) |
+ feature_names = get_conditional_feature_names_from_interface(interface) |
+ if feature_names: |
+ is_global = interface_is_global(interface) |
+ if interface.is_partial: |
+ # For partial interfaces, we need to generate different |
+ # includes if the parent interface is in a different |
+ # component. |
+ parent_interface_info = info_provider.interfaces_info[interface.name] |
+ parent_interface = read_idl_file( |
+ reader, parent_interface_info.get('full_path')) |
+ is_global = is_global or interface_is_global(parent_interface) |
+ parent_component = idl_filename_to_component( |
+ parent_interface_info.get('full_path')) |
+ if interface.is_partial and target_component != parent_component: |
+ includes.add('bindings/%s/v8/V8%s.h' % |
+ (parent_component, interface.name)) |
+ includes.add('bindings/%s/v8/V8%sPartial.h' % |
+ (target_component, interface.name)) |
+ else: |
+ includes.add('bindings/%s/v8/V8%s.h' % |
+ (target_component, interface.name)) |
+ # If this is a partial interface in the same component as |
+ # its parent, then treat it as a non-partial interface. |
+ interface.is_partial = False |
+ interface_info = ConditionalInterfaceInfo(interface.name, |
+ v8_class_name(interface), |
+ v8_class_name_or_partial( |
+ interface), |
+ is_global) |
+ for feature_name in feature_names: |
+ features_for_type[interface_info].add(feature_name) |
+ types_for_feature[feature_name].add(interface_info) |
+ |
+ return features_for_type, types_for_feature, includes |
+ |
+ |
+def conditional_features_context(generator_name, feature_info): |
+ context = {'code_generator': generator_name} |
+ |
+ # Unpack the feature info tuple. |
+ features_for_type, types_for_feature, includes = feature_info |
+ |
+ # Add includes needed for cpp code and normalize. |
+ includes.update([ |
+ "core/context_features/ContextFeatureSettings.h", |
+ "core/dom/ExecutionContext.h", |
+ "core/frame/Frame.h", |
+ "core/origin_trials/OriginTrials.h", |
+ "platform/bindings/ConditionalFeatures.h", |
+ "platform/bindings/ScriptState.h", |
+ # TODO(iclelland): Remove the need to explicitly include this; it is |
+ # here because the ContextFeatureSettings code needs it. |
+ "bindings/core/v8/V8Window.h", |
+ ]) |
+ context['includes'] = normalize_and_sort_includes(includes) |
+ |
+ # For each interface, collect a list of bindings installation functions to |
+ # call, organized by conditional feature. |
+ context['installers_by_interface'] = [ |
+ {"name": interface_info.name, |
+ "is_global": interface_info.is_global, |
+ "v8_class": interface_info.v8_class, |
+ "installers": get_install_functions([interface_info], feature_names)} |
+ for interface_info, feature_names in features_for_type.items()] |
+ context['installers_by_interface'].sort(key=lambda x: x['name']) |
+ |
+ # For each conditional feature, collect a list of bindings installation |
+ # functions to call, organized by interface. |
+ context['installers_by_feature'] = [ |
+ {"name": feature_name, |
+ "installers": get_install_functions(interfaces, [feature_name])} |
+ for feature_name, interfaces in types_for_feature.items()] |
+ context['installers_by_feature'].sort(key=lambda x: x['name']) |
+ |
+ return context |
+ |
+ |
+def parse_options(): |
+ parser = optparse.OptionParser() |
+ parser.add_option('--cache-directory', |
+ help='cache directory, defaults to output directory') |
+ parser.add_option('--output-directory') |
+ parser.add_option('--info-dir') |
+ parser.add_option('--target-component', |
+ type='choice', |
+ choices=['Core', 'Modules'], |
+ help='target component to generate code') |
+ parser.add_option('--idl-files-list') |
+ |
+ options, _ = parser.parse_args() |
+ if options.output_directory is None: |
+ parser.error('Must specify output directory using --output-directory.') |
+ return options |
+ |
+ |
+def generate_conditional_features(info_provider, options, idl_filenames): |
+ reader = IdlReader(info_provider.interfaces_info, options.cache_directory) |
+ jinja_env = initialize_jinja_env(options.cache_directory) |
+ |
+ # Extract the bidirectional mapping of conditional features <-> interfaces |
+ # from the global info provider and the supplied list of IDL files. |
+ feature_info = conditional_features_info(info_provider, |
+ reader, idl_filenames, |
+ options.target_component.lower()) |
+ |
+ # Convert that mapping into the context required for the Jinja2 templates. |
+ template_context = conditional_features_context( |
+ MODULE_PYNAME, feature_info) |
+ |
+ # Generate and write out the header file |
+ header_text = render_template(jinja_env.get_template( |
+ "ConditionalFeaturesFor%s.h.tmpl" % options.target_component.title()), template_context) |
+ header_path = posixpath.join(options.output_directory, |
+ "ConditionalFeaturesFor%s.h" % options.target_component.title()) |
+ write_file(header_text, header_path) |
+ |
+ # Generate and write out the implementation file |
+ cpp_text = render_template(jinja_env.get_template( |
+ "ConditionalFeaturesFor%s.cpp.tmpl" % options.target_component.title()), template_context) |
+ cpp_path = posixpath.join(options.output_directory, |
+ "ConditionalFeaturesFor%s.cpp" % options.target_component.title()) |
+ write_file(cpp_text, cpp_path) |
+ |
+ |
+def main(): |
+ options = parse_options() |
+ |
+ info_provider = create_component_info_provider( |
+ os.path.normpath(options.info_dir), options.target_component.lower()) |
+ idl_filenames = map(str.strip, open(options.idl_files_list)) |
+ |
+ generate_conditional_features(info_provider, options, idl_filenames) |
+ return 0 |
+ |
+ |
+if __name__ == '__main__': |
+ sys.exit(main()) |