diff options
author | danno@chromium.org <danno@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-13 13:26:59 +0000 |
---|---|---|
committer | danno@chromium.org <danno@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-13 13:26:59 +0000 |
commit | 0e6fd884e68670a70a857d092b79fb7e71bf3856 (patch) | |
tree | f4203b7ebbf80f50c52780743692a958b68e4b96 | |
parent | 8fbdbc5938f060c003f6d261e4f31ab2d8bf1a98 (diff) | |
download | chromium_src-0e6fd884e68670a70a857d092b79fb7e71bf3856.zip chromium_src-0e6fd884e68670a70a857d092b79fb7e71bf3856.tar.gz chromium_src-0e6fd884e68670a70a857d092b79fb7e71bf3856.tar.bz2 |
Auto-generate ADM files
This is the first iteration of my work on the policy MCX/ADM/ADMX/HTML generator. I added chrome/app/policy/policy_templates.grd and a grit extension that parses this file and chrome/app/policy/policy_templates.json and then generates the en-US version of chrome.adm from them. The infrastructure is already in place for other languages and output formats.
BUG=49316
TEST=policy_template_generator_unittest.py and manual tests of chrome.adm
Review URL: http://codereview.chromium.org/3068012
Patch from Gabor Feher <gfeher@google.com>.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@56019 0039d316-1c4b-4281-b951-d872f2087c98
17 files changed, 1113 insertions, 1 deletions
diff --git a/chrome/app/policy/policy_templates.grd b/chrome/app/policy/policy_templates.grd new file mode 100644 index 0000000..f9086d2 --- /dev/null +++ b/chrome/app/policy/policy_templates.grd @@ -0,0 +1,194 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- This file contains definitions of resources that are used in policy +templates and will be translated for each locale. --> + +<grit base_dir="." latest_public_release="0" current_release="1" + source_lang_id="en" enc_check="möl"> + <outputs> + <!-- TODO(gfeher) set output basedir to chrome/app/policy instead of the + current Debug/obj/global_intermediate/chrome/app/policy + --> + <output filename="./app/policy/windows/adm/en-US/chrome.adm" type="adm" lang="en" /> + <output filename="./app/policy/windows/adm/de/chrome.adm" type="adm" lang="de" /> + <output filename="./app/policy/windows/adm/hu/chrome.adm" type="adm" lang="hu" /> + </outputs> + <translations> + <!-- TODO(gfeher): add target languages here --> + </translations> + <!-- TODO(gfeher): set allow_pseudo="false" before release --> + <release seq="1" allow_pseudo="true"> + <messages fallback_to_english="false"> + <!-- Strings for ADM/ADMX/MCX policy templates --> + <!-- TODO(gfeher): new wording of these messages might become necessary when + MCX/HTML output is introduced --> + <message name="IDS_POLICY_GROUP_HOMEPAGE_CAPTION" desc="Caption of the 'homepage' policy settings page."> + Configure default homepage + </message> + <message name="IDS_POLICY_GROUP_HOMEPAGE_DESC" desc="Description of the 'homepage' policy settings page."> + Configures the default home page in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> and prevents users from changing homepage preferences. + + The homepage can either be set to a URL you specify or set to the New Tab Page. + + If you specify the New Tab Page as the homepage, the homepage URL location is ignored. + + If you enable this setting, users cannot change their homepage settings in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>. + </message> + <message name="IDS_POLICY_HOMEPAGELOCATION_CAPTION" desc="Caption of the homepage URL text field in the homepage policy settings page."> + The homepage URL: + </message> + <message name="IDS_POLICY_HOMEPAGEISNEWTABPAGE_CAPTION" desc="Caption of the homepage type selection dropdown menu in the homepage policy settings page."> + Homepage type: + </message> + <message name="IDS_POLICY_ENUM_HOMEPAGEISNEWTABPAGE_CAPTION" desc="Label in the 'Homepage type' dropdown menu for selecting new tab page."> + Always use New Tab Page as homepage + </message> + <message name="IDS_POLICY_ENUM_HOMEPAGEISLOCATIONURL_CAPTION" desc="Label in the 'Homepage type' dropdown menu for selecting an URL."> + Always use homepage URL as homepage + </message> + <message name="IDS_POLICY_GROUP_APPLICATIONLOCALE_CAPTION" desc="Caption of the 'application locale' policy settings page."> + Configure the application locale + </message> + <message name="IDS_POLICY_GROUP_APPLICATIONLOCALE_DESC" desc="Description of the 'application locale' policy settings page."> + Configures the application locale in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> and prevents users from changing the locale. + + If you enable this setting, <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> uses the specified locale. If the configured locale is not supported, 'en-US' is used instead. + + If this setting is disabled or not configured, <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> uses either the user-specified preferred locale (if configured), the system locale or the fallback locale 'en-US'. + </message> + <message name="IDS_POLICY_APPLICATIONLOCALEVALUE_CAPTION" desc="Caption of the 'application locale' text field in the 'application locale' policy settings page."> + Application locale: + </message> + <message name="IDS_POLICY_GROUP_ALTERNATEERRORPAGESENABLED_CAPTION" desc="Caption of the 'alternate error pages' policy settings page."> + Enable alternate error pages + </message> + <message name="IDS_POLICY_GROUP_ALTERNATEERRORPAGESENABLED_DESC" desc="Description of the 'alternate error pages' policy settings page."> + Enables the use of alternate error pages that are built into <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> (such as 'page not found') and prevents users from changing this setting. + + If you enable this setting, alternate error pages are used. + + If you disable this setting, alternate error pages are never used. + + If you enable or disable this setting, users cannot change or override this setting in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>. + </message> + <message name="IDS_POLICY_GROUP_SEARCHSUGGESTENABLED_CAPTION" desc="Caption of the 'search suggestions' policy settings page."> + Enable search suggestions + </message> + <message name="IDS_POLICY_GROUP_SEARCHSUGGESTENABLED_DESC" desc="Description of the 'search suggestions' policy settings page."> + Enables search suggestions in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>'s Omnibox and prevents users from changing this setting. + + If you enable this setting, search suggestions are used. + + If you disable this setting, search suggestions are never used. + + If you enable or disable this setting, users cannot change or override this setting in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>. + </message> + <message name="IDS_POLICY_GROUP_DNSPREFETCHINGENABLED_CAPTION" desc="Caption of the 'DNS prefetching' policy settings page."> + Enable DNS prefetching + </message> + <message name="IDS_POLICY_GROUP_DNSPREFETCHINGENABLED_DESC" desc="Description of the 'DNS prefetching' policy settings page."> + Enables DNS prefecthing in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> and prevents users from changing this setting. + + If you enable or disable this setting, users cannot change or override this setting in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>. + </message> + <message name="IDS_POLICY_GROUP_SAFEBROWSINGENABLED_CAPTION" desc="Caption of the 'safe browsing' policy settings page."> + Enable Safe Browsing + </message> + <message name="IDS_POLICY_GROUP_SAFEBROWSINGENABLED_DESC" desc="Description of the 'safe browsing' policy settings page."> + Enables <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>'s Safe Browsing feature and prevents users from changing this setting. + + If you enable this setting, Safe Browsing is always active. + + If you disable this setting, Safe Browsing is never active. + + If you enable or disable this setting, users cannot change or override this setting in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>. + </message> + <message name="IDS_POLICY_GROUP_SYNCDISABLED_CAPTION" desc="Caption of the 'disable sync' policy settings page."> + Disallow synchronization of data with Google + </message> + <message name="IDS_POLICY_GROUP_SYNCDISABLED_DESC" desc="Description of the 'disable sync' policy settings page."> + Disallows data synchronization in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> using Google-hosted synchronization services and prevents users from changing this setting. + + If you enable this setting, users cannot change or override this setting in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>. + </message> + <message name="IDS_POLICY_GROUP_PROXY_CAPTION" desc="Caption of the 'proxy' policy settings page."> + Configure proxy server + </message> + <message name="IDS_POLICY_GROUP_PROXY_DESC" desc="Description of the 'proxy' policy settings page."> + Allows you to specify the proxy server used by <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> and prevents users from changing proxy settings. + + If you choose to never use a proxy server and always connect directly, all other options are ignored. + + If you choose to auto detect the proxy server, all other options are ignored. + + For detailed examples, visit: + http://www.chromium.org/developers/design-documents/network-settings#TOC-Command-line-options-for-proxy-sett + + If you enable this setting, <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> ignores all proxy-related options specified from the command line. + </message> + <message name="IDS_POLICY_PROXYSERVERMODE_CAPTION" desc="Text describing the dropdown menu for selecting proxy server mode in the 'proxy' policy settings page."> + Choose how to specify proxy server settings: + </message> + <message name="IDS_POLICY_ENUM_PROXYSERVERDISABLED_CAPTION" desc="Label in the 'proxy server mode' dropdown menu for selecting no proxy server."> + Never use a proxy + </message> + <message name="IDS_POLICY_ENUM_PROXYSERVERAUTODETECT_CAPTION" desc="Label in the 'proxy server mode' dropdown menu for selecting to auto-detect proxy settings."> + Auto detect proxy settings + </message> + <message name="IDS_POLICY_ENUM_PROXYSERVERMANUAL_CAPTION" desc="Label in the 'proxy server mode' dropdown menu for selecting manual proxy settings."> + Manually specify proxy settings + </message> + <message name="IDS_POLICY_ENUM_PROXYSERVERUSESYSTEM_CAPTION" desc="Label in the 'proxy server mode' dropdown menu for selecting to use system proxy settings."> + Use system proxy settings + </message> + <message name="IDS_POLICY_PROXYSERVER_CAPTION" desc="The label of the 'proxy server address' text field in the 'proxy' policy settings page."> + Address or URL of proxy server: + </message> + <message name="IDS_POLICY_PROXYBYPASSLIST_CAPTION" desc="The label of the 'proxy bypass rules' text field in the 'proxy' policy settings page."> + Comma-separated list of bypass rules: + </message> + <message name="IDS_POLICY_PROXYPACURL_CAPTION" desc="The label of the 'proxy pac URL' text field in the 'proxy' policy settings page."> + URL to a proxy .pac file: + </message> + <message name="IDS_POLICY_GROUP_METRICSREPORTINGENABLED_CAPTION" desc="Caption of the 'crash reporting' policy settings page."> + Enable reporting of usage and crash-related data + </message> + <message name="IDS_POLICY_GROUP_METRICSREPORTINGENABLED_DESC" desc="Description of the 'crash reporting' policy settings page."> + Enables anonymous reporting of usage and crash-related data about <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> to Google and prevents users from changing this setting. + + If you enable this setting, anonymous reporting of usage and crash-related data is sent to Google. + + If you disable this setting, anonymous reporting of usage and crash-related data is never sent to Google. + + If you enable or disable this setting, users cannot change or override this setting in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>. + </message> + <message name="IDS_POLICY_GROUP_PASSWORDMANAGERENABLED_CAPTION" desc="Caption of the 'password manager' policy settings page."> + Enable the password manager + </message> + <message name="IDS_POLICY_GROUP_PASSWORDMANAGERENABLED_DESC" desc="Description of the 'password manager' policy settings page."> + Enables saving passwords and using saved passwords in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>. + + If you enable this setting, users can have <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> memorize passwords and provide them automatically the next time you log into a site. + + If you disable this setting, users are not able to save passwords or use already saved passwords. + + If you enable or disable this setting, users cannot change or override this setting in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>. + </message> + <message name="IDS_POLICY_GROUP_DISABLEDPLUGINS_CAPTION" desc="Caption of the 'disable plugins' policy settings page."> + Specify a list of plugins that are disabled + </message> + <message name="IDS_POLICY_GROUP_DISABLEDPLUGINS_DESC" desc="Description of the 'disable plugins' policy settings page."> + Specifies a list of plugins that are disabled in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> and prevents users from changing this setting. The value is a comma-separated list of the names of plugins to be disabled. + + If you enable this setting, the specified list of plugins is never used in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>. The plugins are marked as disabled in 'about:plugins' and users are not able to enable them. + </message> + <message name="IDS_POLICY_DISABLEDPLUGINSLIST_CAPTION" desc="The label of the 'list of disabled plugins' text field in the 'disabled plugins' policy settings page."> + Comma-separated list of disabled plugins: + </message> + </messages> + + <structures> + <structure name="IDD_POLICY_SOURCE_FILE" file="policy_templates.json" type="policy_template_metafile" /> + </structures> + </release> +</grit> diff --git a/chrome/app/policy/policy_templates.json b/chrome/app/policy/policy_templates.json new file mode 100644 index 0000000..7571abe --- /dev/null +++ b/chrome/app/policy/policy_templates.json @@ -0,0 +1,138 @@ +{ +# policy_templates.json - Metafile for policy templates +# +# This file is used as input to generate the following policy templates: +# ADM (TODO(gfeher): MCX,ADMX,DOC) +# +# Policy templates are user interface definitions or documents about the +# policies that can be used to configure Chrome. Each policy is a name-value +# pair where the value has a given type. Chrome looks up the values using the +# names of the policies. In the user interface where the values can be set, +# related policies might appear together in policy groups. The grouping is not +# visible to Chrome. +# +# This file contains a list of policy groups. Each group contains a list +# of policies under the key 'policies'. If a policy does not belong to +# any groups then it should be placed in its own separate group. All the +# policies and all the groups must have unique names but it is not a problem +# to have a group and policy with the same name. Group names are in fact +# not exposed to Chrome at all. +# +# Each policy has a type. The currently implemented types: +# 'string' - a string value +# 'enum' - the user can select its value from a collection of items +# 'main' - a boolean value with additional semantics: +# A group can have at most one main policy and it must have a value of true +# if the policy group is enabled, false if the group is disabled or no value +# if the policy is not specified. Chrome will see this policy no differently +# than other policies, but depending on the platform, the GUI of the +# templates might reflect the fact that these policies are 'main'. For +# example on Windows this policy will be assigned to the Enable/Disable +# buttons of the group, and if it is set to Disabled then other policies of +# the group will not be visible to Chrome. +# +# Policy group descriptions, policy captions and similar texts are localized +# strings taken from the <message> nodes of the .grd file. Their name +# attributes are generated from the JSON keys. +# Possible locations of localized strings in the templates: +# Policy groups: +# They can have both captions and descriptions. +# Policies: +# Policies with type 'main': +# none (inherits them from the policy group) +# Policies of other types: +# Captions only. +# Selectable item in an 'enum'-typed policy: +# Captions only. +# Generated grd names: +# Each name has two parts: the second part is either CAPTION or DESC, +# and the first part identifies the item the text applies to: +# -For policy groups: +# IDS_POLICY_GROUP_<NAME OF THE GROUP> +# e.g. the name of the description of group Homepage: +# IDS_POLICY_GROUP_HOMEPAGE_DESC +# -For policies: +# IDS_POLICY_<NAME OF THE POLICY> +# e.g. the name of the caption of policy HomepageLocation: +# IDS_POLICY_HOMEPAGELOCATION_CAPTION +# -For enum items: +# IDS_POLICY_ENUM_<NAME OF THE ITEM> +# e.g. the name of the caption of ProxyServerDisabled: +# IDS_POLICY_ENUM_PROXYSERVERDISABLED_CAPTION +# +# TODO(gfeher): ADMX, MCX, DOC, .h/.cc? files +# + 'Homepage': { + 'policies': { + 'HomepageLocation': {'type': 'string'}, + 'HomepageIsNewTabPage': { + 'type': 'enum', + 'items': { + 'HomepageIsLocationURL': {'value': '0'}, + 'HomepageIsNewTabPage': {'value': '1'}, + } + }, + } + }, + 'ApplicationLocale': { + 'policies': { + 'ApplicationLocaleValue': {'type': 'string'}, + } + }, + 'AlternateErrorPagesEnabled': { + 'policies': { + 'AlternateErrorPagesEnabled': {'type': 'main'}, + } + }, + 'SearchSuggestEnabled': { + 'policies': { + 'SearchSuggestEnabled': {'type': 'main'}, + } + }, + 'DnsPrefetchingEnabled': { + 'policies': { + 'DnsPrefetchingEnabled': {'type': 'main'}, + } + }, + 'SafeBrowsingEnabled': { + 'policies': { + 'SafeBrowsingEnabled': {'type': 'main'}, + } + }, + 'MetricsReportingEnabled': { + 'policies': { + 'MetricsReportingEnabled': {'type': 'main'}, + } + }, + 'PasswordManagerEnabled': { + 'policies': { + 'PasswordManagerEnabled': {'type': 'main'}, + } + }, + 'DisabledPlugins': { + 'policies': { + 'DisabledPluginsList': {'type': 'string'}, + } + }, + 'SyncDisabled': { + 'policies': { + 'SyncDisabled': {'type': 'main'}, + } + }, + 'Proxy': { + 'policies': { + 'ProxyServerMode': { + 'type': 'enum', + 'items': { + 'ProxyServerDisabled': {'value': '0'}, + 'ProxyServerAutoDetect': {'value': '1'}, + 'ProxyServerManual': {'value': '2'}, + 'ProxyServerUseSystem': {'value': '3'}, + } + }, + 'ProxyServer': {'type': 'string'}, + 'ProxyPacUrl': {'type': 'string'}, + 'ProxyBypassList': {'type': 'string'}, + } + } +}
\ No newline at end of file diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 21c06da..a68f235 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -48,6 +48,7 @@ 'app/chromium_strings.grd', 'app/generated_resources.grd', 'app/google_chrome_strings.grd', + 'app/policy/policy_templates.grd', ], 'chrome_resources_grds': [ # Data resources. diff --git a/tools/grit/grit/format/policy_templates/__init__.py b/tools/grit/grit/format/policy_templates/__init__.py new file mode 100644 index 0000000..1f159c0 --- /dev/null +++ b/tools/grit/grit/format/policy_templates/__init__.py @@ -0,0 +1,9 @@ +# Copyright (c) 2010 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. + +'''Module grit.format.policy_templates +''' + +pass + diff --git a/tools/grit/grit/format/policy_templates/policy_template_generator.py b/tools/grit/grit/format/policy_templates/policy_template_generator.py new file mode 100644 index 0000000..efbf769 --- /dev/null +++ b/tools/grit/grit/format/policy_templates/policy_template_generator.py @@ -0,0 +1,116 @@ +# Copyright (c) 2010 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. + + +class PolicyTemplateGenerator: + '''Generates template text for a particular platform. + + This class is used to traverse a JSON structure from a .json template + definition metafile and merge GUI message string definitions that come + from a .grd resource tree onto it. After this, it can be used to output + this data to policy template files using TemplateWriter objects. + ''' + + def __init__(self, messages, policy_groups): + '''Initializes this object with all the data necessary to output a + policy template. + + Args: + messages: An identifier to string dictionary of all the localized + messages that might appear in the policy template. + policy_groups: The policies are organized into groups, and each policy + has a name and type. The user will see a GUI interface for assigning + values to policies, by using the templates generated here. The traversed + data structure is a dictionary of policy group names to groups. Each + group is a dictionary and has an embedded dictionary of policies under + the key 'policies'. Example: + policy_groups = { + 'PolicyGroup1': { + 'policies': { + 'Policy1Name': {'type': 'string'} + 'Policy2Name': {'type': 'main'} + 'Policy3Name': {'type': 'enum', 'items': {...}} + } + }, + 'PolicyGroup2': {...} + } + See chrome/app/policy.policy_templates.json for an example and more + details. + ''' + # List of all the policies: + self._policy_groups = policy_groups + # Localized messages to be inserted to the policy_groups structure: + self._messages = messages + self._AddMessagesToPolicies() + + def _AddMessagesToItem(self, item_name, item, warn_no_desc): + '''Adds localized message strings to an item of the policy data structure + (self._policy_groups). + + Args: + item_name: The name of the item. + item: The item which will get its message strings now. + warn_no_desc: A boolean value. If it is True, warnings will be + printed in case there are no description strings for item in + self._messages. + ''' + # The keys for the item's messages in self._messages: + caption_id = 'IDS_POLICY_%s_CAPTION' % item_name + desc_id = 'IDS_POLICY_%s_DESC' % item_name + # Copy the messages from self._messages to item: + if caption_id in self._messages: + item['caption'] = self._messages[caption_id] + else: + print 'Warning: no localized message for ' + caption_id + if desc_id in self._messages: + item['desc'] = self._messages[desc_id] + elif warn_no_desc: + print 'Warning: no localized message for ' + desc_id + + def _AddMessagesToPolicies(self): + '''Adds localized message strings to each item of the policy data structure + (self._policy_groups). + ''' + # Iterate through all the policy groups. + for group_name, group in self._policy_groups.iteritems(): + # Get caption and description for this group. + group_name = 'GROUP_' + group_name.upper() + self._AddMessagesToItem(group_name, group, True) + if 'policies' in group: + # Iterate through all the policies in the current group. + for policy_name, policy in group['policies'].iteritems(): + if policy['type'] == 'enum': + # Iterate through all the items of an enum-type policy. + for item_name, item in policy['items'].iteritems(): + self._AddMessagesToItem('ENUM_' + item_name.upper(), item, False) + elif policy['type'] == 'main': + # In this case, messages are inherited from the group. + policy['caption'] = group['caption'] + policy['desc'] = group['desc'] + continue + # Get caption for this policy. + full_name = policy_name.upper() + self._AddMessagesToItem(full_name, policy, False) + + def GetTemplateText(self, template_writer): + '''Generates the text of the template from the arguments given + to the constructor, using a given TemplateWriter. + + Args: + template_writer: An object implementing TemplateWriter. Its methods + are called here for each item of self._policy_groups. + + Returns: + The text of the generated template. + ''' + template_writer.Prepare() + template_writer.BeginTemplate() + for group_name, group in self._policy_groups.iteritems(): + template_writer.BeginPolicyGroup(group_name, group) + if 'policies' in group: + for policy_name, policy in group['policies'].iteritems(): + template_writer.WritePolicy(policy_name, policy) + template_writer.EndPolicyGroup() + template_writer.EndTemplate() + return template_writer.GetTemplateText() diff --git a/tools/grit/grit/format/policy_templates/policy_template_generator_unittest.py b/tools/grit/grit/format/policy_templates/policy_template_generator_unittest.py new file mode 100644 index 0000000..7f5923b9 --- /dev/null +++ b/tools/grit/grit/format/policy_templates/policy_template_generator_unittest.py @@ -0,0 +1,220 @@ +# Copyright (c) 2010 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 unittest + +from policy_template_generator import PolicyTemplateGenerator +from writers.mock_writer import MockWriter + + +class PolicyTemplateGeneratorUnittest(unittest.TestCase): + '''Unit tests for policy_template_generator.py.''' + + def do_test(self, strings, policy_groups, writer): + '''Executes a test case. + + Creates and invokes an instance of PolicyTemplateGenerator with + the given arguments. + + Notice: Plain comments are used in test methods instead of docstrings, + so that method names do not get overridden by the docstrings in the + test output. + + Args: + strings: The dictionary of localized strings. + policy_groups: The list of policy groups as it would be loaded from + policy_templates.json. + writer: A writer used for this test. It is usually derived from + mock_writer.MockWriter. + ''' + writer.tester = self + policy_generator = PolicyTemplateGenerator(strings, policy_groups) + res = policy_generator.GetTemplateText(writer) + writer.Test() + return res + + def testSequence(self): + # Test the sequence of invoking the basic PolicyWriter operations, + # in case of empty input data structures. + class LocalMockWriter(MockWriter): + def __init__(self): + self.log = 'init;' + def Prepare(self): + self.log += 'prepare;' + def BeginTemplate(self): + self.log += 'begin;' + def EndTemplate(self): + self.log += 'end;' + def GetTemplateText(self): + self.log += 'get_text;' + return 'writer_result_string' + def Test(self): + self.tester.assertEquals(self.log, + 'init;prepare;begin;end;get_text;') + result = self.do_test({}, {}, LocalMockWriter()) + self.assertEquals(result, 'writer_result_string') + + def testEmptyGroups(self): + # Test that in case of three empty policy groups, each one is passed to + # the writer. + strings_mock = { + 'IDS_POLICY_GROUP_GROUP1_CAPTION': None, + 'IDS_POLICY_GROUP_GROUP1_DESC': None, + 'IDS_POLICY_GROUP_GROUP2_CAPTION': None, + 'IDS_POLICY_GROUP_GROUP2_DESC': None, + 'IDS_POLICY_GROUP_GROUP3_CAPTION': None, + 'IDS_POLICY_GROUP_GROUP3_DESC': None, + } + policies_mock = { + 'Group1': {'tag': 'T1'}, + 'Group2': {'tag': 'T2'}, + 'Group3': {'tag': 'T3'}, + } + class LocalMockWriter(MockWriter): + def __init__(self): + self.log = '' + self.set = set() + def BeginPolicyGroup(self, group_name, group): + self.log += '[' + self.set.add ( (group_name, group['tag']) ) + def EndPolicyGroup(self): + self.log += ']' + def Test(self): + self.tester.assertEquals(self.log, '[][][]') + self.tester.assertEquals( + self.set, + set([('Group1', 'T1'), ('Group2', 'T2'), ('Group3', 'T3')])) + self.do_test(strings_mock, policies_mock, LocalMockWriter()) + + def testGroupTexts(self): + # Test that GUI strings are assigned correctly to policy groups. + strings_mock = { + 'IDS_POLICY_GROUP_GROUP1_CAPTION': 'string1', + 'IDS_POLICY_GROUP_GROUP1_DESC': 'string2', + 'IDS_POLICY_GROUP_GROUP2_CAPTION': 'string3', + 'IDS_POLICY_GROUP_GROUP2_DESC': 'string4', + } + policies_mock = { + 'Group1': {}, + 'Group2': {}, + } + class LocalMockWriter(MockWriter): + def BeginPolicyGroup(self, group_name, group): + if group_name == 'Group1': + self.tester.assertEquals(group['caption'], 'string1') + self.tester.assertEquals(group['desc'], 'string2') + elif group_name == 'Group2': + self.tester.assertEquals(group['caption'], 'string3') + self.tester.assertEquals(group['desc'], 'string4') + else: + self.tester.fail() + self.do_test(strings_mock, policies_mock, LocalMockWriter()) + + def testPolicies(self): + # Test that policies are passed correctly to the writer. + strings_mock = { + 'IDS_POLICY_GROUP_GROUP1_CAPTION': None, + 'IDS_POLICY_GROUP_GROUP1_DESC': None, + 'IDS_POLICY_GROUP_GROUP2_CAPTION': None, + 'IDS_POLICY_GROUP_GROUP2_DESC': None, + 'IDS_POLICY_GROUP1POLICY1_CAPTION': None, + 'IDS_POLICY_GROUP1POLICY2_CAPTION': None, + 'IDS_POLICY_GROUP2POLICY3_CAPTION': None, + } + policies_mock = { + 'Group1': { + 'policies': { + 'Group1Policy1': {'type': 'string'}, + 'Group1Policy2': {'type': 'string'} + } + }, + 'Group2': { + 'policies': { + 'Group2Policy3': {'type': 'string'} + } + } + } + class LocalMockWriter(MockWriter): + def __init__(self): + self.policy_name = None + self.policy_set = set() + def BeginPolicyGroup(self, group_name, group): + self.group_name = group_name + def EndPolicyGroup(self): + self.group_name = None + def WritePolicy(self, group_name, group): + self.tester.assertEquals(group_name[0:6], self.group_name) + self.policy_set.add(group_name) + def Test(self): + self.tester.assertEquals( + self.policy_set, + set(['Group1Policy1', 'Group1Policy2', 'Group2Policy3'])) + self.do_test(strings_mock, policies_mock, LocalMockWriter()) + + def testPolicyTexts(self): + # Test that GUI strings are assigned correctly to policies. + strings_mock = { + 'IDS_POLICY_POLICY1_CAPTION': 'string1', + 'IDS_POLICY_POLICY1_DESC': 'string2', + 'IDS_POLICY_POLICY2_CAPTION': 'string3', + 'IDS_POLICY_POLICY2_DESC': 'string4', + 'IDS_POLICY_GROUP_GROUP1_CAPTION': None, + 'IDS_POLICY_GROUP_GROUP1_DESC': None, + } + policies_mock = { + 'Group1': { + 'policies': { + 'Policy1': {'type': 'string'}, + 'Policy2': {'type': 'string'} + } + } + } + class LocalMockWriter(MockWriter): + def WritePolicy(self, policy_name, policy): + if policy_name == 'Policy1': + self.tester.assertEquals(policy['caption'], 'string1') + self.tester.assertEquals(policy['desc'], 'string2') + elif policy_name == 'Policy2': + self.tester.assertEquals(policy['caption'], 'string3') + self.tester.assertEquals(policy['desc'], 'string4') + else: + self.tester.fail() + self.do_test(strings_mock, policies_mock, LocalMockWriter()) + + def testEnumTexts(self): + # Test that GUI strings are assigned correctly to enums + # (aka dropdown menus). + strings_mock = { + 'IDS_POLICY_ENUM_ITEM1_CAPTION': 'string1', + 'IDS_POLICY_ENUM_ITEM2_CAPTION': 'string2', + 'IDS_POLICY_ENUM_ITEM3_CAPTION': 'string3', + 'IDS_POLICY_POLICY1_CAPTION': None, + 'IDS_POLICY_GROUP_GROUP1_CAPTION': None, + 'IDS_POLICY_GROUP_GROUP1_DESC': None, + } + policies_mock = { + 'Group1': { + 'policies': { + 'Policy1': { + 'type': 'enum', + 'items': { + 'item1': {'value': '0'}, + 'item2': {'value': '1'}, + 'item3': {'value': '3'}, + } + } + } + } + } + class LocalMockWriter(MockWriter): + def WritePolicy(self, policy_name, policy): + self.tester.assertEquals(policy['items']['item1']['caption'], 'string1') + self.tester.assertEquals(policy['items']['item2']['caption'], 'string2') + self.tester.assertEquals(policy['items']['item3']['caption'], 'string3') + self.do_test(strings_mock, policies_mock, LocalMockWriter()) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/grit/grit/format/policy_templates/template_formatter.py b/tools/grit/grit/format/policy_templates/template_formatter.py new file mode 100644 index 0000000..47fd474 --- /dev/null +++ b/tools/grit/grit/format/policy_templates/template_formatter.py @@ -0,0 +1,130 @@ +# Copyright (c) 2010 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. + + +from grit.format import interface +from grit.node import structure +from grit.node import message +from grit.node import misc +from policy_template_generator import PolicyTemplateGenerator +import types +import os +import sys + + +class TemplateFormatter(interface.ItemFormatter): + '''Creates a template file corresponding to an <output> node of the grit + tree. + + More precisely, processes the whole grit tree for a given <output> node whose + type is 'adm'. TODO(gfeher) add new types here + The result of processing is a policy template file with the + given type and language of the <output> node. A new instance of this class + is created by grit.misc.GritNode for each <output> node. This class does + the interfacing with grit, but the actual template-generating work is done in + policy_template_generator.PolicyTemplateGenerator. + ''' + + def __init__(self, writer_name): + '''Initializes this formatter to output messages with a given writer. + + Args: + writer_name: A string identifying the TemplateWriter subclass used + for generating the output. If writer name is 'adm', then the class + from module 'writers.adm_writer' will be used. + ''' + super(type(self), self).__init__() + writer_module_name = \ + 'grit.format.policy_templates.writers.' + writer_name + '_writer' + __import__(writer_module_name) + # The module that contains the writer class: + self._writer_module = sys.modules[writer_module_name] + + def Format(self, item, lang='en', begin_item=True, output_dir='.'): + '''Generates a template corresponding to an <output> node in the grd file. + + Args: + item: the <grit> root node of the grit tree. + lang: the language of outputted text, e.g.: 'en' + begin_item: True or False, depending on if this function was called at + the beginning or at the end of the item. + output_dir: The output directory, currently unused here. + + Returns: + The text of the template file. + ''' + if not begin_item: + return '' + + self._lang = lang + self._SetBranding(item) + self._policy_groups = None + self._messages = {} + self._ParseGritNodes(item) + return self._GetOutput() + + def _GetOutput(self): + '''Generates a template file using the instance variables initialized + in Format() using the writer specified in __init__(). + + Returns: + The text of the policy template based on the parameters passed + to __init__() and Format(). + ''' + policy_generator = \ + PolicyTemplateGenerator(self._messages, self._policy_groups) + writer = self._writer_module.GetWriter(self._build) + str = policy_generator.GetTemplateText(writer) + return str + + def _SetBranding(self, item): + '''Sets self._branding and self._build based on the -D_chromium or + -D_google_chrome command line switch of grit. + + Args: + item: The <grit> root node of the grit tree. + ''' + defines = item.defines + if '_chromium' in defines: + self._branding = 'Chromium' + self._build = 'chromium' + elif '_google_chrome' in defines: + self._branding = 'Google Chrome' + self._build = 'chrome' + else: + raise Exception('Unknown build') + + def _ImportMessage(self, message): + '''Takes a grit message node and adds its translated content to + self._messages. + + Args: + message: A <message> node in the grit tree. + ''' + msg_name = message.GetTextualIds()[0] + msg_txt = message.Translate(self._lang) + msg_txt = msg_txt.replace('$1', self._branding) + lines = msg_txt.split('\n') + lines = [line.strip() for line in lines] + msg_txt = "\\n".join(lines) + self._messages[msg_name] = msg_txt + + def _ParseGritNodes(self, item): + '''Collects the necessary information from the grit tree: + the message strings and the policy definitions. + + Args: + item: The grit node parsed currently. + ''' + nodes = [] + if (isinstance(item, misc.IfNode) and not item.IsConditionSatisfied()): + return + if (isinstance(item, structure.StructureNode) and + item.attrs['type'] == 'policy_template_metafile'): + assert self._policy_groups == None + self._policy_groups = item.gatherer.GetData() + elif (isinstance(item, message.MessageNode)): + self._ImportMessage(item) + for child in item.children: + self._ParseGritNodes(child) diff --git a/tools/grit/grit/format/policy_templates/writers/__init__.py b/tools/grit/grit/format/policy_templates/writers/__init__.py new file mode 100644 index 0000000..68aab64 --- /dev/null +++ b/tools/grit/grit/format/policy_templates/writers/__init__.py @@ -0,0 +1,9 @@ +# Copyright (c) 2010 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. + +'''Module grit.format.policy_templates.writers +''' + +pass + diff --git a/tools/grit/grit/format/policy_templates/writers/adm_writer.py b/tools/grit/grit/format/policy_templates/writers/adm_writer.py new file mode 100644 index 0000000..e7b7c36 --- /dev/null +++ b/tools/grit/grit/format/policy_templates/writers/adm_writer.py @@ -0,0 +1,119 @@ +# Copyright (c) 2010 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. + + +from template_writer import TemplateWriter + + +def GetWriter(build): + return AdmWriter(build) + + +class AdmWriter(TemplateWriter): + '''Class for generating policy templates in Windows ADM format. + It is used by PolicyTemplateGenerator to write ADM files. + ''' + + TYPE_TO_INPUT = { + 'string': 'EDITTEXT', + 'enum': 'DROPDOWNLIST'} + NEWLINE = '\r\n' + + def _AddGuiString(self, name, value): + line = '%s="%s"' % (name, value) + self.str_list.append(line) + + def _PrintLine(self, string='', indent_diff=0): + '''Prints a string with indents and a linebreak to the output. + + Args: + string: The string to print. + indent_diff: the difference of indentation of the printed line, + compared to the next/previous printed line. Increment occurs + after printing the line, while decrement occurs before that. + ''' + indent_diff *= 2 + if indent_diff < 0: + self.indent = self.indent[(-indent_diff):] + self.policy_list.append(self.indent + string) + if indent_diff > 0: + self.indent += ''.ljust(indent_diff) + + def _WriteSupported(self): + self._PrintLine('#if version >= 4', 1) + self._PrintLine('SUPPORTED !!SUPPORTED_WINXPSP2') + self._PrintLine('#endif', -1) + + def WritePolicy(self, policy_name, policy): + type = policy['type'] + if type == 'main': + self._PrintLine('VALUENAME "%s"' % policy_name ) + self._PrintLine('VALUEON NUMERIC 1') + self._PrintLine('VALUEOFF NUMERIC 0') + return + + policy_part_name = policy_name + '_Part' + self._AddGuiString(policy_part_name, policy['caption']) + + self._PrintLine() + self._PrintLine( + 'PART !!%s %s' % (policy_part_name, self.TYPE_TO_INPUT[type]), + 1) + self._PrintLine('VALUENAME "%s"' % policy_name) + if type == 'enum': + self._PrintLine('ITEMLIST', 1) + for item_name, item in policy['items'].iteritems(): + self._PrintLine( + 'NAME !!%s_DropDown VALUE NUMERIC %s' % (item_name, item['value'])) + self._AddGuiString(item_name + '_DropDown', item['caption']) + self._PrintLine('END ITEMLIST', -1) + self._PrintLine('END PART', -1) + + def BeginPolicyGroup(self, group_name, group): + group_explain_name = group_name + '_Explain' + self._AddGuiString(group_name + '_Policy', group['caption']) + self._AddGuiString(group_explain_name, group['desc']) + + self._PrintLine('POLICY !!%s_Policy' % group_name, 1) + self._WriteSupported() + self._PrintLine('EXPLAIN !!' + group_explain_name) + + def EndPolicyGroup(self): + self._PrintLine('END POLICY', -1) + self._PrintLine() + + def BeginTemplate(self): + # TODO(gfeher): Move this string to .grd. + self._AddGuiString('SUPPORTED_WINXPSP2', + 'At least Microsoft Windows XP SP2') + self._PrintLine('CLASS MACHINE', 1) + if self.build == 'chrome': + self._AddGuiString('google', 'Google') + self._AddGuiString('googlechrome', 'Google Chrome') + self._PrintLine('CATEGORY !!google', 1) + self._PrintLine('CATEGORY !!googlechrome', 1) + self._PrintLine('KEYNAME "Software\\Policies\\Google\\Google Chrome"') + elif self.build == 'chromium': + self._AddGuiString('chromium', 'Chromium') + self._PrintLine('CATEGORY !!chromium', 1) + self._PrintLine('KEYNAME "Software\\Policies\\Chromium"') + self._PrintLine() + + def EndTemplate(self): + if self.build == 'chrome': + self._PrintLine('END CATEGORY', -1) + self._PrintLine('END CATEGORY', -1) + self._PrintLine('', -1) + elif self.build == 'chromium': + self._PrintLine('END CATEGORY', -1) + self._PrintLine('', -1) + + def Prepare(self): + self.policy_list = [] + self.str_list = ['[Strings]'] + self.indent = '' + + def GetTemplateText(self): + lines = self.policy_list + self.str_list + return self.NEWLINE.join(lines) diff --git a/tools/grit/grit/format/policy_templates/writers/mock_writer.py b/tools/grit/grit/format/policy_templates/writers/mock_writer.py new file mode 100644 index 0000000..8700b68 --- /dev/null +++ b/tools/grit/grit/format/policy_templates/writers/mock_writer.py @@ -0,0 +1,38 @@ +# Copyright (c) 2010 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. + + +from template_writer import TemplateWriter + + +class MockWriter(TemplateWriter): + '''Helper class for unit tests in policy_template_generator_unittest.py + ''' + + def __init__(self): + pass + + def WritePolicy(self, policy_name, policy): + pass + + def BeginPolicyGroup(self, group_name, group): + pass + + def EndPolicyGroup(self): + pass + + def BeginTemplate(self): + pass + + def EndTemplate(self): + pass + + def Prepare(self): + pass + + def GetTemplateText(self): + pass + + def Test(self): + pass diff --git a/tools/grit/grit/format/policy_templates/writers/template_writer.py b/tools/grit/grit/format/policy_templates/writers/template_writer.py new file mode 100644 index 0000000..cb80fb2 --- /dev/null +++ b/tools/grit/grit/format/policy_templates/writers/template_writer.py @@ -0,0 +1,71 @@ +# Copyright (c) 2010 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. + + +class TemplateWriter(object): + '''Abstract base class for writing policy templates in various formats. + The methods of this class will be called by PolicyTemplateGenerator. + ''' + + def __init__(self, build): + '''Initializes a TemplateWriter object. + + Args: + build: 'chrome' or 'chromium' + ''' + assert build in ['chrome', 'chromium'] + self.build = build + + def Prepare(self): + '''Initializes the internal buffer where the template will be + stored. + ''' + raise NotImplementedError() + + def WritePolicy(self, policy_name, policy): + '''Appends the template text corresponding to a policy into the + internal buffer. + + Args: + policy_name: The name of the policy that has to be written. + policy: The policy as it is found in the JSON file. + ''' + raise NotImplementedError() + + def BeginPolicyGroup(self, group_name, group): + '''Appends the template text corresponding to the beginning of a + policy group into the internal buffer. + + Args: + group_name: The name of the policy group that has to be written. + group: The policy group as it is found in the JSON file. + ''' + raise NotImplementedError() + + def EndPolicyGroup(self): + '''Appends the template text corresponding to the end of a + policy group into the internal buffer. + ''' + raise NotImplementedError() + + def BeginTemplate(self): + '''Appends the text corresponding to the beginning of the whole + template into the internal buffer. + ''' + raise NotImplementedError() + + def EndTemplate(self): + '''Appends the text corresponding to the end of the whole + template into the internal buffer. + ''' + raise NotImplementedError() + + + def GetTemplateText(self): + '''Gets the content of the internal template buffer. + + Returns: + The generated template from the the internal buffer as a string. + ''' + raise NotImplementedError() diff --git a/tools/grit/grit/gather/json_loader.py b/tools/grit/grit/gather/json_loader.py new file mode 100644 index 0000000..e2518e4 --- /dev/null +++ b/tools/grit/grit/gather/json_loader.py @@ -0,0 +1,53 @@ +# Copyright (c) 2010 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 types + +from grit.gather import interface +from grit import util + + +class JsonLoader(interface.GathererBase): + '''A simple gatherer that loads and parses a JSON file.''' + + def __init__(self, json_text): + '''Initializes a gatherer object with JSON input. + + Args: + json_text: A string containing a JSON expression. + ''' + super(type(self), self).__init__() + self._json_text = json_text + self._data = None + + def Parse(self): + '''Parses the text of self._json_text into the data structure in + self._data. + ''' + globs = {} + exec('data = ' + self._json_text, globs) + self._data = globs['data'] + + def GetData(self): + '''Returns the parsed JSON data.''' + return self._data + + def FromFile(filename_or_stream, extkey, encoding): + '''Creates a JSONLoader instance from a file or stream. + + Args: + filename_or_stream: The source of JSON data. + extkey: Unused, see interface.py. + encoding: The encoding used in the JSON file. (Note that it should + not contain localized strings.) + + Returns: + The JSONLoader instance holding the JSON data unparsed. + ''' + if isinstance(filename_or_stream, types.StringTypes): + filename_or_stream = \ + util.WrapInputStream(file(filename_or_stream, 'rU'), encoding) + return JsonLoader(filename_or_stream.read()) + FromFile = staticmethod(FromFile) diff --git a/tools/grit/grit/node/misc.py b/tools/grit/grit/node/misc.py index 6ede0c6..56fec47 100644 --- a/tools/grit/grit/node/misc.py +++ b/tools/grit/grit/node/misc.py @@ -228,6 +228,9 @@ class GritNode(base.Node): elif t == 'js_map_format': from grit.format import js_map_format return js_map_format.TopLevel() + elif t == 'adm': + from grit.format.policy_templates import template_formatter + return template_formatter.TemplateFormatter(t) else: return super(type(self), self).ItemFormatter(t) diff --git a/tools/grit/grit/node/structure.py b/tools/grit/grit/node/structure.py index 7058cd3..95eac76 100644 --- a/tools/grit/grit/node/structure.py +++ b/tools/grit/grit/node/structure.py @@ -15,6 +15,7 @@ from grit import constants from grit import exception from grit import util +import grit.gather.json_loader import grit.gather.rc import grit.gather.tr_html import grit.gather.admin_template @@ -44,6 +45,7 @@ _GATHERERS = { 'tr_html' : grit.gather.tr_html.TrHtml, 'txt' : grit.gather.txt.TxtFile, 'version' : grit.gather.rc.Version, + 'policy_template_metafile' : grit.gather.json_loader.JsonLoader, } @@ -59,6 +61,7 @@ _RC_FORMATTERS = { 'tr_html' : grit.format.rc.RcInclude('HTML'), 'txt' : grit.format.rc.RcInclude('TXT'), 'version' : grit.format.rc.RcSection(), + 'policy_template_metafile': None, } diff --git a/tools/grit/grit/test_suite_all.py b/tools/grit/grit/test_suite_all.py index cb2d087..98b9d0d 100644 --- a/tools/grit/grit/test_suite_all.py +++ b/tools/grit/grit/test_suite_all.py @@ -46,6 +46,7 @@ class TestSuiteAll(unittest.TestSuite): from grit.gather import muppet_strings_unittest from grit.node.custom import filename_unittest import grit.format.js_map_format_unittest + from grit.format.policy_templates import policy_template_generator_unittest test_classes = [ base_unittest.NodeUnittest, @@ -75,6 +76,7 @@ class TestSuiteAll(unittest.TestSuite): muppet_strings_unittest.MuppetStringsUnittest, filename_unittest.WindowsFilenameUnittest, grit.format.js_map_format_unittest.JsMapFormatUnittest, + policy_template_generator_unittest.PolicyTemplateGeneratorUnittest, # add test classes here... ] diff --git a/tools/grit/grit/tool/build.py b/tools/grit/grit/tool/build.py index 0a79769..9d1c6bc1 100644 --- a/tools/grit/grit/tool/build.py +++ b/tools/grit/grit/tool/build.py @@ -185,6 +185,7 @@ are exported to translation interchange files (e.g. XMB files), etc. elif output.GetType() == 'js_map_format': encoding = 'utf_8' else: + # TODO(gfeher) modify here to set utf-8 encoding for admx/adml encoding = 'utf_16' # Make the output directory if it doesn't exist. diff --git a/tools/grit/resource_ids b/tools/grit/resource_ids index 93905dd..f33298f 100644 --- a/tools/grit/resource_ids +++ b/tools/grit/resource_ids @@ -116,5 +116,10 @@ "gfx/gfx_resources.grd": { "includes": [18500], - } + }, + + "chrome/app/policy/policy_templates.grd": { + "structures": [19000], + "messages": [19010], + }, } |