Commit c8d60a66 authored by Yashar Dabiran's avatar Yashar Dabiran Committed by Commit Bot

Add feature policy parser helper functions

This CL is part of the changes needed for this design doc:
https://docs.google.com/document/d/1Qog5qwZ-6Rfx10AKkJxN-LrwhCw_GJ1xdLoQSnpsT1Q

This CL provides a way to define feature policy features in a declarative way
inside "feature_policy_features.json5" which generates the required code
needed by the parser.

The parser does not use the generated functions yet (it will be done in
an upcoming CL). A few features are added to "feature_policy_features.json5"
to manually test the generated code. Once all features are defined in
the .json5 file and the parser uses the helper functions, the code will be
tested against all existing tests for feature policy features.

Bug: 934491
Change-Id: I44e5a919f1dbc0d6a5abae327f68ba3c90abb5d7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1534290
Commit-Queue: Yashar Dabiran <yashard@google.com>
Reviewed-by: default avatarDavid Bokan <bokan@chromium.org>
Reviewed-by: default avatarLuna Lu <loonybear@chromium.org>
Cr-Commit-Position: refs/heads/master@{#644818}
parent a597b417
# Copyright 2019 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.
import make_runtime_features_utilities as util
import json5_generator
import template_expander
class FeaturePolicyFeatureWriter(json5_generator.Writer):
file_basename = 'feature_policy_helper'
def __init__(self, json5_file_path, output_dir):
super(FeaturePolicyFeatureWriter, self).__init__(json5_file_path, output_dir)
self._outputs = {
(self.file_basename + '.cc'): self.generate_implementation,
}
self._features = self.json5_file.name_dictionaries
# Set runtime and feature policy features
self._runtime_features = []
self._feature_policy_features = []
for feature in self._features:
if feature['feature_policy_name']:
self._feature_policy_features.append(feature)
else:
self._runtime_features.append(feature)
self._convert_runtime_name_to_feature()
self._set_in_origin_trial()
self._runtime_to_feature_policy_map = self._make_runtime_to_feature_policy_map()
self._origin_trial_dependency_map = self._make_origin_trial_dependency_map()
self._header_guard = self.make_header_guard(self._relative_output_dir + self.file_basename + '.h')
# Replaces runtime names in 'depends_on' list to feature objects.
def _convert_runtime_name_to_feature(self):
name_to_runtime_feature_map = {}
for feature in self._runtime_features:
name_to_runtime_feature_map[str(feature['name'])] = feature
def replace_name_with_object(name):
assert name in name_to_runtime_feature_map, name + ' is not a runtime feature.'
return name_to_runtime_feature_map[name]
for feature in self._feature_policy_features:
feature['depends_on'] = map(replace_name_with_object, feature['depends_on'])
# Returns a map of runtime features (not in origin trial) to the feature
# policy features that depend on them. The maps is used to generate
# DefaultFeatureNameMap().
def _make_runtime_to_feature_policy_map(self):
runtime_to_feature_policy_map = {}
for feature in self._feature_policy_features:
# Filter out all the ones not in OT.
for dependant in [runtime_feature for runtime_feature
in feature['depends_on']
if not runtime_feature['in_origin_trial']]:
dependant_name = str(dependant['name'])
if dependant_name not in runtime_to_feature_policy_map:
runtime_to_feature_policy_map[dependant_name] = [feature]
else:
runtime_to_feature_policy_map[dependant_name].append(feature)
return runtime_to_feature_policy_map
# Returns a map of feature policy features that depend on OT features to
# their dependencies. The map is used to generate DisabledByOriginTrial().
def _make_origin_trial_dependency_map(self):
origin_trial_dependency_map = {}
for feature in [fp_feature for fp_feature
in self._feature_policy_features
if fp_feature['in_origin_trial']]:
feature_name = str(feature['name'])
# Only go through the dependencies that are in an origin trial.
for dependant in [runtime_feature for runtime_feature
in feature['depends_on']
if runtime_feature['in_origin_trial']]:
if feature_name in origin_trial_dependency_map:
origin_trial_dependency_map[feature_name].append(dependant)
else:
origin_trial_dependency_map[feature_name] = [dependant]
return origin_trial_dependency_map
# Sets the 'in_origin_trial' flag for features. A feature is in an origin
# trial if either itself or one of its dependencies are in an origin trial.
def _set_in_origin_trial(self):
# |_dependency_graph| is used to keep the 'depends_on' relationship
# for the runtime features defined in "runtime_enabled_features.json5".
self._dependency_graph = util.init_graph(self._runtime_features)
for feature in self._runtime_features:
for dependant_name in feature['depends_on']:
assert dependant_name in self._dependency_graph, dependant_name + ' is not a feature.'
self._dependency_graph[dependant_name].append(str(feature['name']))
util.check_if_dependency_graph_contains_cycle(self._dependency_graph)
util.set_origin_trials_features(self._runtime_features, self._dependency_graph)
# Set the flag for feature policy features as well.
for feature in self._feature_policy_features:
feature['in_origin_trial'] = False
if any([runtime_feature['in_origin_trial'] for
runtime_feature in feature['depends_on']]):
feature['in_origin_trial'] = True
def _template_inputs(self):
return {
'feature_policy_features': self._feature_policy_features,
'header_guard': self._header_guard,
'input_files': self._input_files,
'runtime_features': self._runtime_features,
'runtime_to_feature_policy_map': self._runtime_to_feature_policy_map,
'origin_trial_dependency_map': self._origin_trial_dependency_map,
}
@template_expander.use_jinja('templates/' + file_basename + '.cc.tmpl')
def generate_implementation(self):
return self._template_inputs()
if __name__ == '__main__':
json5_generator.Maker(FeaturePolicyFeatureWriter).main()
{% from 'templates/macros.tmpl' import license, source_files_for_generated_file %}
{{license()}}
{{source_files_for_generated_file(template_file, input_files)}}
#include "third_party/blink/renderer/core/feature_policy/feature_policy_helper.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
namespace blink {
namespace {
using FeatureNameMapping = HashMap<String, mojom::FeaturePolicyFeature>;
{% for feature in feature_policy_features %}
const char k{{feature.name}}PolicyName[] = "{{feature.feature_policy_name}}";
{% endfor %}
} // namespace
// Features which depend on a flag also have the same flag controlling whether
// they are in this map. Features which are shipping as part of an origin trial
// add their feature names to this map unconditionally, as the trial token could
// be added after the HTTP header needs to be parsed. This also means that
// top-level documents which simply want to embed another page which uses an
// origin trial feature, without using the feature themselves, can use feature
// policy to allow use of the feature in subframes (The framed document will
// still require a valid origin trial token to use the feature in this scenario).
const FeatureNameMapping& DefaultFeatureNameMap() {
DEFINE_STATIC_LOCAL(FeatureNameMapping, default_feature_name_map, ());
if (default_feature_name_map.IsEmpty()) {
{% for feature in feature_policy_features %}
{% if not feature.depends_on or feature.in_origin_trial %}
default_feature_name_map.Set(k{{feature.name}}PolicyName,
mojom::FeaturePolicyFeature::k{{feature.name}});
{% endif %}
{% endfor %}
{% for runtime_feature_name, FP_features in runtime_to_feature_policy_map.items() %}
if (RuntimeEnabledFeatures::{{runtime_feature_name}}Enabled()) {
{% for feature in FP_features %}
default_feature_name_map.Set(k{{feature.name}}PolicyName,
mojom::FeaturePolicyFeature::k{{feature.name}});
{% endfor %}
}
{% endfor %}
}
return default_feature_name_map;
}
bool DisabledByOriginTrial(const String& feature_name,
ExecutionContext* execution_context) {
{% for feature_name, dependencies in origin_trial_dependency_map.items() %}
if (feature_name == k{{feature_name}}PolicyName) {
return
{%- for dependency in dependencies %}
{%- if not loop.first %} &&{% endif %}
!RuntimeEnabledFeatures::{{dependency.name}}Enabled(execution_context)
{%- endfor %};
}
{% endfor %}
return false;
}
} // namespace blink
\ No newline at end of file
...@@ -1470,6 +1470,33 @@ blink_python_runner("make_core_generated_css_primitive_value_unit_trie") { ...@@ -1470,6 +1470,33 @@ blink_python_runner("make_core_generated_css_primitive_value_unit_trie") {
deps = make_core_generated_deps deps = make_core_generated_deps
} }
blink_python_runner("make_core_generated_feature_policy_helper") {
script = "../build/scripts/make_feature_policy_helper.py"
inputs = scripts_for_json5_files + [
"../build/scripts/make_feature_policy_helper.py",
"./feature_policy/feature_policy_features.json5",
"../build/scripts/templates/feature_policy_helper.cc.tmpl",
]
outputs = [
"$blink_core_output_dir/feature_policy/feature_policy_helper.cc",
]
args = [
rebase_path("../platform/runtime_enabled_features.json5", root_build_dir),
rebase_path("./feature_policy/feature_policy_features.json5",
root_build_dir),
"--output_dir",
"$rel_blink_core_gen_dir/feature_policy",
]
if (is_mac && !use_system_xcode) {
args += [
"--developer_dir",
hermetic_xcode_path,
]
}
}
blink_python_runner("make_core_generated_html_element_lookup_trie") { blink_python_runner("make_core_generated_html_element_lookup_trie") {
visibility = [] # Allow re-assignment of list. visibility = [] # Allow re-assignment of list.
visibility = [ ":*" ] visibility = [ ":*" ]
...@@ -1592,6 +1619,7 @@ targets_generating_sources = [ ...@@ -1592,6 +1619,7 @@ targets_generating_sources = [
":make_core_generated_event_names", ":make_core_generated_event_names",
":make_core_generated_event_target_names", ":make_core_generated_event_target_names",
":make_core_generated_event_type_names", ":make_core_generated_event_type_names",
":make_core_generated_feature_policy_helper",
":make_core_generated_html_element_factory", ":make_core_generated_html_element_factory",
":make_core_generated_html_element_lookup_trie", ":make_core_generated_html_element_lookup_trie",
":make_core_generated_html_entity_table", ":make_core_generated_html_entity_table",
......
...@@ -11,6 +11,7 @@ blink_core_sources("feature_policy") { ...@@ -11,6 +11,7 @@ blink_core_sources("feature_policy") {
"dom_feature_policy.h", "dom_feature_policy.h",
"feature_policy.cc", "feature_policy.cc",
"feature_policy.h", "feature_policy.h",
"feature_policy_helper.h",
"iframe_policy.h", "iframe_policy.h",
"layout_animations_policy.cc", "layout_animations_policy.cc",
"layout_animations_policy.h", "layout_animations_policy.h",
......
{
// All feature policy (https://github.com/w3c/webappsec-feature-policy)
// features are defined here.
// All Features have to be defined in FeaturePolicyFeature enum as well
// (defined in third_party/blink/public/mojom/feature_policy/feature_policy.mojom).
// The enum value has to have the same name as the feature name here.
// This list is used to generate "feature_policy_helper.cc".
// TODO(yashard): Add all feature policy features here.
parameters: {
// "depends_on" specifies relationship to runtime features defined
// in "runtime_enabled_features.json5":
// depends_on: ["feature1", "feature2", ...]
// * If the depends_on features are *only* runtime features, the feature is
// available if any of the runtime features are enabled.
// * If the depends_on list includes origin trial features, the feature is
// available if any of the origin trial features are enabled.
depends_on: {
default: [],
valid_type: "list",
},
// feature_policy_name: "FEATURE_NAME" is used to specify the policy name
// which gets parsed from the header or the allow attribute.
feature_policy_name: {
},
},
data: [
{
name: "Frobulate",
feature_policy_name: "frobulate",
depends_on: ["OriginTrialsSampleAPI"],
},
{
name: "LazyLoad",
feature_policy_name: "lazyload",
depends_on: ["ExperimentalProductivityFeatures"],
},
{
name: "SyncXHR",
feature_policy_name: "sync-xhr",
},
{
name: "WebVr",
feature_policy_name: "vr",
depends_on: ["WebVR", "WebXR"],
},
],
}
\ No newline at end of file
// Copyright 2019 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FEATURE_POLICY_FEATURE_POLICY_HELPER_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_FEATURE_POLICY_FEATURE_POLICY_HELPER_H_
#include "third_party/blink/public/common/feature_policy/feature_policy.h"
#include "third_party/blink/renderer/platform/wtf/hash_map.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink {
class ExecutionContext;
// This method defines the feature names which will be recognized by the parser
// for the Feature-Policy HTTP header and the <iframe> "allow" attribute, as
// well as the features which will be recognized by the document or iframe
// policy object.
const HashMap<String, mojom::FeaturePolicyFeature>& DefaultFeatureNameMap();
// Returns true if this feature is currently disabled by an origin trial (it is
// origin trial controlled, and the origin trial is not enabled).
bool DisabledByOriginTrial(const String&, ExecutionContext*);
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_FEATURE_POLICY_FEATURE_POLICY_HELPER_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment