Reland "Add auto generated documentation about atomic policy groups"

This reverts commit e43038e9048ebe94637f4d8be79dceafb795d3d8.
This relands commit 5d3ecdf99f71f71e5ae4da9ca99182d39b7034bb with a fix.

Original change's description:
> Add auto generated documentation about atomic policy groups
>
> Bug: 962669
> Change-Id: Ie6333108a9bbe06c16eeb3895b17df855b705f4e
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1612103
> Reviewed-by: Nico Weber <thakis@chromium.org>
> Reviewed-by: Lutz Justen <ljusten@chromium.org>
> Commit-Queue: Yann Dago <ydago@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#668579}

TBR=thakis@chromium.org

Bug: 962669
Change-Id: Ib2d1c82c9f32257c6a7158737e4a152d5fb3b667
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1663079
Commit-Queue: Yann Dago <ydago@chromium.org>
Reviewed-by: Lutz Justen <ljusten@chromium.org>
Cr-Commit-Position: refs/heads/master@{#670465}
diff --git a/components/policy/BUILD.gn b/components/policy/BUILD.gn
index 0087025..e094f46 100644
--- a/components/policy/BUILD.gn
+++ b/components/policy/BUILD.gn
@@ -12,6 +12,10 @@
 
 assert(!is_ios, "Policy should not be referenced on iOS")
 
+# To generate policy documentation for local use, set this to true for the
+# links between pages and subpages to work.
+gen_policy_templates_local = false
+
 # To test policy generation for platforms different than your OS, override and
 # enable these flags (but don't check that in!).
 gen_policy_templates_common = true
@@ -192,11 +196,16 @@
            chrome_version_path,
          ] + grit_defines
 
+  if (gen_policy_templates_local) {
+    args += [ "--local" ]
+  }
   if (gen_policy_templates_common) {
     outputs += policy_templates_common_outputs
     args += [
       "--doc",
       rebase_path(policy_templates_doc_path, root_build_dir),
+      "--doc_atomic_groups",
+      rebase_path(policy_templates_doc_atomic_groups_path, root_build_dir),
     ]
   }
   if (gen_policy_templates_android) {
@@ -453,6 +462,8 @@
       "--add",
       rebase_path(policy_templates_doc_path, root_build_dir),
       "--add",
+      rebase_path(policy_templates_doc_atomic_groups_path, root_build_dir),
+      "--add",
       rebase_path(policy_templates_win_reg_path, root_build_dir),
       "--add",
       rebase_path(policy_templates_win_adm_path, root_build_dir),
diff --git a/components/policy/resources/policy_templates.gni b/components/policy/resources/policy_templates.gni
index 37b97378..7699bf3 100644
--- a/components/policy/resources/policy_templates.gni
+++ b/components/policy/resources/policy_templates.gni
@@ -152,6 +152,9 @@
 # Common outputs.
 policy_templates_doc_path =
     "$policy_templates_base_dir/common/html/\${lang}/chrome_policy_list.html"
+policy_templates_doc_atomic_groups_path =
+    "$policy_templates_base_dir/common/" +
+    "html/\${lang}/chrome_policy_atomic_groups_list.html"
 policy_templates_common_outputs = []
 foreach(lang, policy_templates_languages) {
   policy_templates_common_outputs += [
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 1a7a698..3f07770 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -16215,6 +16215,18 @@
 
 The recommended way to configure policy on Windows is via GPO, although provisioning policy via registry is still supported for Windows instances that are joined to a <ph name="MS_AD_NAME">Microsoft® Active Directory®</ph> domain.'''
     },
+    'doc_group_intro': {
+      'desc': '''Introduction text for the generated policy atomic group documentation''',
+      'text': '''Both Chromium and Google Chrome have some groups of policies that depend on each other to provide control over a feature. These sets are represented by the following policy groups. Given that policies can have multiple sources, only values coming from the highest priority source will be applied. Values coming from a lower priority source in the same group will be ignored. The order of priority is defined in <ph name="POLICY_PRIORITY_DOC_URL">https://support.google.com/chrome/a/?p=policy_order<ex>https://support.google.com/chrome/a/?p=policy_order</ex></ph>.'''
+    },
+    'doc_policy_in_atomic_group': {
+      'desc': '''Label notifying that a policy is part of an atomic policy group''',
+      'text': '''This policy is part of the following atomic group (only policies from the highest priority source present in the group are applied) :'''
+    },
+    'doc_policy_atomic_group': {
+      'desc': '''Caption text of the 'policy atomic group' in the summary chart of a policy in the generated documentation''',
+      'text': '''Policy atomic group:'''
+    },
     'doc_back_to_top': {
       'desc': '''Text of a link in the generated policy documentation, that takes the user to the top of the page''',
       'text': '''Back to top'''
@@ -16462,6 +16474,7 @@
     {
       'id': 1,
       'name': 'Homepage',
+      'caption': '''Homepage''',
       'policies': [
         'HomepageLocation',
         'HomepageIsNewTabPage',
@@ -16472,6 +16485,7 @@
     {
       'id': 2,
       'name': 'RemoteAccess',
+      'caption': '''Remote access''',
       'policies': [
         'RemoteAccessClientFirewallTraversal',
         'RemoteAccessHostClientDomain',
@@ -16498,6 +16512,7 @@
     {
       'id': 3,
       'name': 'PasswordManager',
+      'caption': '''Password manager''',
       'policies': [
         'PasswordManagerEnabled',
         'PasswordManagerAllowShowPasswords',
@@ -16506,6 +16521,7 @@
     {
       'id': 4,
       'name': 'Proxy',
+      'caption': '''Proxy''',
       'policies': [
         'ProxyMode',
         'ProxyServerMode',
@@ -16518,6 +16534,7 @@
     {
       'id': 5,
       'name': 'Extensions',
+      'caption': '''Extensions''',
       'policies': [
         'ExtensionInstallBlacklist',
         'ExtensionInstallWhitelist',
@@ -16530,7 +16547,8 @@
     },
     {
       'id': 6,
-      'name': 'RestoreOnStartupGroup',
+      'name': 'RestoreOnStartup',
+      'caption': '''Action on startup''',
       'policies': [
         'RestoreOnStartup',
         'RestoreOnStartupURLs',
@@ -16539,6 +16557,7 @@
     {
       'id': 7,
       'name': 'DefaultSearchProvider',
+      'caption': '''Default search provider''',
       'policies': [
         'DefaultSearchProviderEnabled',
         'DefaultSearchProviderName',
@@ -16561,6 +16580,7 @@
     {
       'id': 8,
       'name': 'ImageSettings',
+      'caption': '''Image settings''',
       'policies': [
         'DefaultImagesSetting',
         'ImagesAllowedForUrls',
@@ -16570,6 +16590,7 @@
     {
       'id': 9,
       'name': 'CookiesSettings',
+      'caption': '''Cookies settings''',
       'policies': [
         'DefaultCookiesSetting',
         'CookiesAllowedForUrls',
@@ -16580,6 +16601,7 @@
     {
       'id': 10,
       'name': 'JavascriptSettings',
+      'caption': '''Javascript settings''',
       'policies': [
         'DefaultJavaScriptSetting',
         'JavaScriptAllowedForUrls',
@@ -16589,6 +16611,7 @@
     {
       'id': 11,
       'name': 'PluginsSettings',
+      'caption': '''Plugins settings''',
       'policies': [
         'DefaultPluginsSetting',
         'PluginsAllowedForUrls',
@@ -16598,6 +16621,7 @@
     {
       'id': 12,
       'name': 'PopupsSettings',
+      'caption': '''Popups settings''',
       'policies': [
         'DefaultPopupsSetting',
         'PopupsAllowedForUrls',
@@ -16607,6 +16631,7 @@
     {
       'id': 13,
       'name': 'KeygenSettings',
+      'caption': '''Keygen settings''',
       'policies': [
         'DefaultKeygenSetting',
         'KeygenAllowedForUrls',
@@ -16616,6 +16641,7 @@
     {
       'id': 14,
       'name': 'NotificationsSettings',
+      'caption': '''Notification settings''',
       'policies': [
         'DefaultNotificationsSetting',
         'NotificationsAllowedForUrls',
@@ -16625,6 +16651,7 @@
     {
       'id': 15,
       'name': 'WebUsbSettings',
+      'caption': '''Web USB settings''',
       'policies': [
         'DefaultWebUsbGuardSetting',
         'DeviceWebUsbAllowDevicesForUrls',
@@ -16636,6 +16663,7 @@
     {
       'id': 16,
       'name': 'NativeMessaging',
+      'caption': '''Native messaging''',
       'policies': [
         'NativeMessagingBlacklist',
         'NativeMessagingWhitelist',
@@ -16645,6 +16673,7 @@
     {
       'id': 17,
       'name': 'Drive',
+      'caption': '''Drive''',
       'policies': [
         'DriveDisabled',
         'DriveDisabledOverCellular',
@@ -16653,6 +16682,7 @@
     {
       'id': 18,
       'name': 'Attestation',
+      'caption': '''Attestation''',
       'policies': [
         'AttestationEnabledForDevice',
         'AttestationEnabledForUser',
@@ -16663,6 +16693,7 @@
     {
       'id': 19,
       'name': 'ContentPack',
+      'caption': '''Content pack''',
       'policies': [
         'ContentPackDefaultFilteringBehavior',
         'ContentPackManualBehaviorHosts',
@@ -16672,6 +16703,7 @@
     {
       'id': 20,
       'name': 'SupervisedUsers',
+      'caption': '''Supervised users''',
       'policies': [
         'SupervisedUsersEnabled',
         'SupervisedUserCreationEnabled',
@@ -16681,6 +16713,7 @@
     {
       'id': 21,
       'name': 'GoogleCast',
+      'caption': '''Google Cast''',
       'policies': [
         'CastReceiverEnabled',
         'CastReceiverName',
@@ -16689,6 +16722,7 @@
     {
       'id': 22,
       'name': 'QuickUnlock',
+      'caption': '''Quick unlock''',
       'policies': [
         'QuickUnlockModeWhitelist',
         'QuickUnlockTimeout',
@@ -16697,6 +16731,7 @@
     {
       'id': 23,
       'name': 'PinUnlock',
+      'caption': '''Pin unlock''',
       'policies': [
         'PinUnlockMinimumLength',
         'PinUnlockMaximumLength',
@@ -16706,6 +16741,7 @@
     {
       'id': 24,
       'name': 'SafeBrowsing',
+      'caption': '''Safe Browsing settings''',
       'policies': [
         'SafeBrowsingEnabled',
         'SafeBrowsingExtendedReportingEnabled',
@@ -16716,6 +16752,7 @@
     {
       'id': 25,
       'name': 'PasswordProtection',
+      'caption': '''Password protection''',
       'policies': [
         'PasswordProtectionWarningTrigger',
         'PasswordProtectionLoginURLs',
@@ -16725,6 +16762,7 @@
     {
       'id': 26,
       'name': 'NetworkFileShares',
+      'caption': '''Network File Shares settings''',
       'policies': [
         'NetworkFileSharesAllowed',
         'NetBiosShareDiscoveryEnabled',
@@ -16735,6 +16773,7 @@
     {
       'id': 27,
       'name': 'ChromeReportingExtension',
+      'caption': '''Chrome Reporting Extension''',
       'policies': [
         'ReportVersionData',
         'ReportPolicyData',
@@ -16748,6 +16787,7 @@
     {
       'id': 28,
       'name': 'BrowserSwitcher',
+      'caption': '''Legacy Browser Support''',
       'policies': [
         'AlternativeBrowserPath',
         'AlternativeBrowserParameters',
@@ -16766,6 +16806,7 @@
     {
       'id': 29,
       'name': 'PluginVm',
+      'caption': '''PluginVm''',
       'policies': [
         'PluginVmAllowed',
         'PluginVmLicenseKey',
@@ -16774,7 +16815,8 @@
     },
     {
       'id': 30,
-      'name': 'DeviceSAML',
+      'name': 'SAML',
+      'caption': '''SAML''',
       'policies': [
         'DeviceSamlLoginAuthenticationType',
         'DeviceTransferSAMLCookies',
@@ -16782,7 +16824,8 @@
     },
     {
       'id': 31,
-      'name': 'DeviceLoginScreenOrigins',
+      'name': 'LoginScreenOrigins',
+      'caption': '''Login and screen origins''',
       'policies': [
         'DeviceLoginScreenIsolateOrigins',
         'DeviceLoginScreenSitePerProcess',
@@ -16791,6 +16834,7 @@
     {
       'id': 32,
       'name': 'UserAndDeviceReporting',
+      'caption': '''User and device reporting''',
       'policies': [
         'ReportDeviceVersionInfo',
         'ReportDeviceBootMode',
@@ -16813,7 +16857,8 @@
     },
     {
       'id': 33,
-      'name': 'DeviceWiFi',
+      'name': 'WiFi',
+      'caption': '''WiFi''',
       'policies': [
         'DeviceWiFiFastTransitionEnabled',
         'DeviceWiFiAllowed'
@@ -16822,6 +16867,7 @@
     {
       'id': 34,
       'name': 'Kiosk',
+      'caption': '''Kiosk settings''',
       'policies': [
         'DeviceLocalAccounts',
         'DeviceLocalAccountAutoLoginId',
@@ -16833,6 +16879,7 @@
     {
       'id': 35,
       'name': 'DateAndTime',
+      'caption': '''Date and time''',
       'policies': [
         'SystemTimezone',
         'SystemTimezoneAutomaticDetection',
@@ -16841,6 +16888,7 @@
     {
       'id': 36,
       'name': 'Display',
+      'caption': '''Display''',
       'policies': [
         'DeviceDisplayResolution',
         'DisplayRotationDefault',
@@ -16849,6 +16897,7 @@
     {
       'id': 37,
       'name': 'ActiveDirectoryManagement',
+      'caption': '''<ph name="MS_AD_NAME">Microsoft® Active Directory®</ph> management settings''',
       'policies': [
         'DeviceMachinePasswordChangeRate',
         'DeviceUserPolicyLoopbackProcessingMode',
diff --git a/components/policy/tools/template_writers/policy_template_generator.py b/components/policy/tools/template_writers/policy_template_generator.py
index 89dd732..bedb927 100755
--- a/components/policy/tools/template_writers/policy_template_generator.py
+++ b/components/policy/tools/template_writers/policy_template_generator.py
@@ -7,6 +7,10 @@
 import re
 
 
+def IsGroupOrAtomicGroup(policy):
+  return policy['type'] == 'group' or policy['type'] == 'atomic_group'
+
+
 class PolicyTemplateGenerator:
   '''Generates template text for a particular platform.
 
@@ -60,6 +64,14 @@
     for key in self._messages.keys():
       self._messages[key]['text'] = self._ImportMessage(
           self._messages[key]['text'])
+    self._AddAtomicGroups(self._policy_data['policy_definitions'],
+                          self._policy_data['policy_atomic_group_definitions'])
+    self._policy_data[
+        'policy_atomic_group_definitions'] = self._ExpandAtomicGroups(
+            self._policy_data['policy_definitions'],
+            self._policy_data['policy_atomic_group_definitions'])
+    self._ProcessPolicyList(
+        self._policy_data['policy_atomic_group_definitions'])
     self._policy_data['policy_definitions'] = self._ExpandGroups(
         self._policy_data['policy_definitions'])
     self._policy_definitions = self._policy_data['policy_definitions']
@@ -125,20 +137,21 @@
       policy: The data structure of the policy or group, that will get message
         strings here.
     '''
-    policy['desc'] = self._ImportMessage(policy['desc'])
+    if policy['type'] != 'atomic_group':
+      policy['desc'] = self._ImportMessage(policy['desc'])
     policy['caption'] = self._ImportMessage(policy['caption'])
     if 'label' in policy:
       policy['label'] = self._ImportMessage(policy['label'])
     if 'arc_support' in policy:
       policy['arc_support'] = self._ImportMessage(policy['arc_support'])
 
-    if policy['type'] == 'group':
+    if IsGroupOrAtomicGroup(policy):
       self._ProcessPolicyList(policy['policies'])
     elif policy['type'] in ('string-enum', 'int-enum', 'string-enum-list'):
       # Iterate through all the items of an enum-type policy, and add captions.
       for item in policy['items']:
         item['caption'] = self._ImportMessage(item['caption'])
-    if policy['type'] != 'group':
+    if not IsGroupOrAtomicGroup(policy):
       if not 'label' in policy:
         # If 'label' is not specified, then it defaults to 'caption':
         policy['label'] = policy['caption']
@@ -171,6 +184,43 @@
     policy_data_copy = copy.deepcopy(self._policy_data)
     return template_writer.WriteTemplate(policy_data_copy)
 
+  def _AddAtomicGroups(self, policy_list, policy_atomic_groups):
+    '''Adds an 'atomic_group' field to the policies that are part of an atomic
+    group.
+
+    Args:
+      policy_list: A list of policies and groups.
+      policy_atomic_groups: A list of policy atomic groups
+    '''
+    policy_lookup = {
+        policy['name']: policy
+        for policy in policy_list
+        if not IsGroupOrAtomicGroup(policy)
+    }
+    for group in policy_atomic_groups:
+      for policy_name in group['policies']:
+        policy_lookup[policy_name]['atomic_group'] = group['name']
+        break
+
+  def _ExpandAtomicGroups(self, policy_list, policy_atomic_groups):
+    '''Replaces policies names inside atomic group definitions for actual
+    policies definitions.
+
+    Args:
+      policy_list: A list of policies and groups.
+
+    Returns:
+      Modified policy_list
+    '''
+    policies = [
+        policy for policy in policy_list if not IsGroupOrAtomicGroup(policy)
+    ]
+    for group in policy_atomic_groups:
+      group['type'] = 'atomic_group'
+    expanded = self._ExpandGroups(policies + policy_atomic_groups)
+    expanded = [policy for policy in expanded if IsGroupOrAtomicGroup(policy)]
+    return copy.deepcopy(expanded)
+
   def _ExpandGroups(self, policy_list):
     '''Replaces policies names inside group definitions for actual policies
     definitions. If policy does not belong to any group, leave it as is.
@@ -181,11 +231,11 @@
     Returns:
       Modified policy_list
     '''
-    groups = [policy for policy in policy_list if policy['type'] == 'group']
+    groups = [policy for policy in policy_list if IsGroupOrAtomicGroup(policy)]
     policies = {
         policy['name']: policy
         for policy in policy_list
-        if policy['type'] != 'group'
+        if not IsGroupOrAtomicGroup(policy)
     }
     policies_in_groups = set()
     result_policies = []
@@ -200,7 +250,7 @@
       result_policies.append(group)
 
     result_policies.extend([
-        policy for policy in policy_list if policy['type'] != 'group' and
+        policy for policy in policy_list if not IsGroupOrAtomicGroup(policy) and
         policy['name'] not in policies_in_groups
     ])
     return result_policies
diff --git a/components/policy/tools/template_writers/policy_template_generator_unittest.py b/components/policy/tools/template_writers/policy_template_generator_unittest.py
index f80c3e6..9b5fd3e 100755
--- a/components/policy/tools/template_writers/policy_template_generator_unittest.py
+++ b/components/policy/tools/template_writers/policy_template_generator_unittest.py
@@ -28,6 +28,7 @@
       'messages': {},
       'placeholders': [],
       'policy_definitions': [],
+      'policy_atomic_group_definitions': [],
   }
 
   def do_test(self, policy_data, writer):
diff --git a/components/policy/tools/template_writers/template_formatter.py b/components/policy/tools/template_writers/template_formatter.py
index 8f6f24f..73c74304 100755
--- a/components/policy/tools/template_writers/template_formatter.py
+++ b/components/policy/tools/template_writers/template_formatter.py
@@ -20,7 +20,8 @@
                     chromeos_admx_writer, chromeos_adml_writer, \
                     google_admx_writer, google_adml_writer, \
                     android_policy_writer, reg_writer, doc_writer, \
-                    json_writer, plist_writer, plist_strings_writer
+                    doc_atomic_groups_writer , json_writer, plist_writer, \
+                    plist_strings_writer
 
 
 def MacLanguageMap(lang):
@@ -59,6 +60,7 @@
     WriterDesc('android_policy', False, 'utf-8', None, False),
     WriterDesc('reg', False, 'utf-16', None, False),
     WriterDesc('doc', True, 'utf-8', None, False),
+    WriterDesc('doc_atomic_groups', True, 'utf-8', None, False),
     WriterDesc('json', False, 'utf-8', None, False),
     WriterDesc('plist', False, 'utf-8', None, False),
     WriterDesc('plist_strings', True, 'utf-8', MacLanguageMap, False)
@@ -148,6 +150,13 @@
   parser.add_option('--google_admx', action='append', dest='google_admx')
   parser.add_option('--reg', action='append', dest='reg')
   parser.add_option('--doc', action='append', dest='doc')
+  parser.add_option(
+      '--doc_atomic_groups', action='append', dest='doc_atomic_groups')
+  parser.add_option(
+      '--local',
+      action='store_true',
+      help='If set, the documentation will be built so \
+            that links work locally in the generated path.')
   parser.add_option('--json', action='append', dest='json')
   parser.add_option('--plist', action='append', dest='plist')
   parser.add_option('--plist_strings', action='append', dest='plist_strings')
@@ -165,6 +174,7 @@
 
   config = _GetWriterConfiguration(options.grit_defines)
   config['major_version'] = _ParseVersionFile(options.version_path)
+  config['local'] = options.local
 
   # For each language, load policy data once and run all writers on it.
   for lang in languages:
diff --git a/components/policy/tools/template_writers/writers/adm_writer_unittest.py b/components/policy/tools/template_writers/writers/adm_writer_unittest.py
index 4bcd50f..adf28950 100755
--- a/components/policy/tools/template_writers/writers/adm_writer_unittest.py
+++ b/components/policy/tools/template_writers/writers/adm_writer_unittest.py
@@ -71,6 +71,7 @@
     policy_json = '''
       {
         'policy_definitions': [],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s
       }''' % MESSAGES
@@ -101,6 +102,7 @@
     policy_json = '''
       {
         'policy_definitions': [],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s
       }''' % MESSAGES
@@ -142,6 +144,7 @@
             'desc': 'Description of main.',
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s
       }''' % MESSAGES
@@ -210,6 +213,7 @@
             'desc': 'Description of main.',
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s
       }''' % MESSAGES
@@ -266,6 +270,7 @@
             'caption': 'Caption of policy.',
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s
       }''' % MESSAGES
@@ -332,6 +337,7 @@
             'supported_on': ['chrome.win:8-']
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s
       }''' % MESSAGES
@@ -400,6 +406,7 @@
           },
         ],
         'placeholders': [],
+        'policy_atomic_group_definitions': [],
         'messages': %s
       }''' % MESSAGES
     output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm')
@@ -477,6 +484,7 @@
             'features': { 'can_be_recommended': True },
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s
       }''' % MESSAGES
@@ -562,6 +570,7 @@
             'features': { 'can_be_recommended': True },
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s
       }''' % MESSAGES
@@ -643,6 +652,7 @@
             'label': 'Label of list policy.'
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s,
       }''' % MESSAGES
@@ -717,6 +727,7 @@
             'label': 'Label of list policy.'
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s
       }''' % MESSAGES
@@ -783,6 +794,7 @@
             'caption': 'Caption of policy.',
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s
       }''' % MESSAGES
@@ -850,6 +862,7 @@
             'caption': 'Caption of policy.',
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s
       }''' % MESSAGES
@@ -923,6 +936,7 @@
             'desc': 'Desc of list policy.',
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s
       }''' % MESSAGES
@@ -960,6 +974,7 @@
             'desc': 'Description of main.',
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s
       }''' % MESSAGES
@@ -1031,6 +1046,7 @@
 With a newline."""
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s
       }''' % MESSAGES
@@ -1133,6 +1149,7 @@
             'supported_on': ['chrome.win:39-'],
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s
       }''' % MESSAGES
@@ -1215,6 +1232,7 @@
             'desc': """Description of policy1."""
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': %s
       }''' % MESSAGES
diff --git a/components/policy/tools/template_writers/writers/doc_atomic_groups_writer.py b/components/policy/tools/template_writers/writers/doc_atomic_groups_writer.py
new file mode 100755
index 0000000..b7b4b0c
--- /dev/null
+++ b/components/policy/tools/template_writers/writers/doc_atomic_groups_writer.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+# 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.
+
+from writers import doc_writer
+
+
+def GetWriter(config):
+  '''Factory method for creating DocAtomicGroupsWriter objects.
+  See the constructor of TemplateWriter for description of
+  arguments.
+  '''
+  return DocAtomicGroupsWriter(['*'], config)
+
+
+class DocAtomicGroupsWriter(doc_writer.DocWriter):
+  '''Class for generating atomic policy group templates in HTML format.
+  The intended use of the generated file is to upload it on
+  https://www.chromium.org, therefore its format has some limitations:
+  - No HTML and body tags.
+  - Restricted set of element attributes: for example no 'class'.
+  Because of the latter the output is styled using the 'style'
+  attributes of HTML elements. This is supported by the dictionary
+  self._STYLES[] and the method self._AddStyledElement(), they try
+  to mimic the functionality of CSS classes. (But without inheritance.)
+
+  This class is invoked by PolicyTemplateGenerator to create the HTML
+  files.
+  '''
+
+  def _AddPolicyRow(self, parent, policy):
+    '''Adds a row for the policy in the summary table.
+
+    Args:
+      parent: The DOM node of the summary table.
+      policy: The data structure of the policy.
+    '''
+    tr = self._AddStyledElement(parent, 'tr', ['tr'])
+    indent = 'padding-left: %dpx;' % (7 + self._indent_level * 14)
+    if policy['type'] != 'group':
+      # Normal policies get two columns with name and caption.
+      name_td = self._AddStyledElement(tr, 'td', ['td', 'td.left'],
+                                       {'style': indent})
+      policy_ref = './'
+      if self.config.get('local', False):
+        policy_ref = './chrome_policy_list.html'
+      self.AddElement(name_td, 'a', {'href': policy_ref + '#' + policy['name']},
+                      policy['name'])
+      self._AddStyledElement(tr, 'td', ['td', 'td.right'], {},
+                             policy['caption'])
+    else:
+      # Groups get one column with caption.
+      name_td = self._AddStyledElement(tr, 'td', ['td', 'td.left'], {
+          'style': indent,
+          'colspan': '2'
+      })
+      self.AddElement(name_td, 'a', {'name': policy['name']}, policy['caption'])
+
+  #
+  # Implementation of abstract methods of TemplateWriter:
+  #
+
+  def WritePolicy(self, policy):
+    self._AddPolicyRow(self._summary_tbody, policy)
+
+  def BeginTemplate(self):
+    self._BeginTemplate('group_intro')
+
+  def WriteTemplate(self, template):
+    '''Writes the given template definition.
+
+    Args:
+      template: Template definition to write.
+
+    Returns:
+      Generated output for the passed template definition.
+    '''
+    self.messages = template['messages']
+    self.Init()
+
+    policies = self.PreprocessPolicies(
+        template['policy_atomic_group_definitions'])
+
+    self.BeginTemplate()
+    for policy in policies:
+      if policy['type'] != 'atomic_group':
+        continue
+      self.BeginPolicyGroup(policy)
+      for child_policy in policy['policies']:
+        # Nesting of groups is currently not supported.
+        self.WritePolicy(child_policy)
+      self.EndPolicyGroup()
+    self.EndTemplate()
+
+    return self.GetTemplateText()
diff --git a/components/policy/tools/template_writers/writers/doc_writer.py b/components/policy/tools/template_writers/writers/doc_writer.py
index 2f283d5..5753b10 100755
--- a/components/policy/tools/template_writers/writers/doc_writer.py
+++ b/components/policy/tools/template_writers/writers/doc_writer.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# 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.
 
@@ -613,6 +613,15 @@
       # Don't add an example for Google cloud managed ChromeOS policies.
       dd = self._AddPolicyAttribute(dl, 'example_value')
       self._AddExample(dd, policy)
+    if 'atomic_group' in policy:
+      dd = self._AddPolicyAttribute(dl, 'policy_atomic_group')
+      policy_group_ref = './policy-list-3/atomic_groups'
+      if 'local' in self.config and self.config['local']:
+        policy_group_ref = './chrome_policy_atomic_groups_list.html'
+      self.AddText(dd, self.GetLocalizedMessage('policy_in_atomic_group') + ' ')
+      self.AddElement(dd, 'a',
+                      {'href': policy_group_ref + '#' + policy['atomic_group']},
+                      policy['atomic_group'])
 
   def _AddPolicyNote(self, parent, policy):
     '''If a policy has an additional web page assigned with it, then add
@@ -696,6 +705,33 @@
       return schema['minimum'] != 0
     return False
 
+  def _BeginTemplate(self, intro_message_id):
+    # Add a <div> for the summary section.
+    if self._GetChromiumVersionString() is not None:
+      self.AddComment(self._main_div, self.config['build'] + \
+          ' version: ' + self._GetChromiumVersionString())
+
+    summary_div = self.AddElement(self._main_div, 'div')
+    self.AddElement(summary_div, 'a', {'name': 'top'})
+    self.AddElement(summary_div, 'br')
+    self._AddParagraphs(summary_div, self.GetLocalizedMessage(intro_message_id))
+    self.AddElement(summary_div, 'br')
+    self.AddElement(summary_div, 'br')
+    self.AddElement(summary_div, 'br')
+    # Add the summary table of policies.
+    summary_table = self._AddStyledElement(summary_div, 'table', ['table'])
+    # Add the first row.
+    thead = self.AddElement(summary_table, 'thead')
+    tr = self._AddStyledElement(thead, 'tr', ['tr'])
+    self._AddStyledElement(tr, 'td', ['td', 'td.left', 'thead td'], {},
+                           self.GetLocalizedMessage('name_column_title'))
+    self._AddStyledElement(tr, 'td', ['td', 'td.right', 'thead td'], {},
+                           self.GetLocalizedMessage('description_column_title'))
+    self._summary_tbody = self.AddElement(summary_table, 'tbody')
+
+    # Add a <div> for the detailed policy listing.
+    self._details_div = self.AddElement(self._main_div, 'div')
+
   #
   # Implementation of abstract methods of TemplateWriter:
   #
@@ -715,31 +751,7 @@
     self._indent_level -= 1
 
   def BeginTemplate(self):
-    # Add a <div> for the summary section.
-    if self._GetChromiumVersionString() is not None:
-      self.AddComment(self._main_div, self.config['build'] + \
-          ' version: ' + self._GetChromiumVersionString())
-
-    summary_div = self.AddElement(self._main_div, 'div')
-    self.AddElement(summary_div, 'a', {'name': 'top'})
-    self.AddElement(summary_div, 'br')
-    self._AddParagraphs(summary_div, self.GetLocalizedMessage('intro'))
-    self.AddElement(summary_div, 'br')
-    self.AddElement(summary_div, 'br')
-    self.AddElement(summary_div, 'br')
-    # Add the summary table of policies.
-    summary_table = self._AddStyledElement(summary_div, 'table', ['table'])
-    # Add the first row.
-    thead = self.AddElement(summary_table, 'thead')
-    tr = self._AddStyledElement(thead, 'tr', ['tr'])
-    self._AddStyledElement(tr, 'td', ['td', 'td.left', 'thead td'], {},
-                           self.GetLocalizedMessage('name_column_title'))
-    self._AddStyledElement(tr, 'td', ['td', 'td.right', 'thead td'], {},
-                           self.GetLocalizedMessage('description_column_title'))
-    self._summary_tbody = self.AddElement(summary_table, 'tbody')
-
-    # Add a <div> for the detailed policy listing.
-    self._details_div = self.AddElement(self._main_div, 'div')
+    self._BeginTemplate('intro')
 
   def Init(self):
     dom_impl = minidom.getDOMImplementation('')
diff --git a/components/policy/tools/template_writers/writers/doc_writer_unittest.py b/components/policy/tools/template_writers/writers/doc_writer_unittest.py
index 872dbed..cd7d7ab1e 100755
--- a/components/policy/tools/template_writers/writers/doc_writer_unittest.py
+++ b/components/policy/tools/template_writers/writers/doc_writer_unittest.py
@@ -135,6 +135,12 @@
         'doc_bla': {
             'text': '_test_bla'
         },
+        'doc_policy_atomic_group': {
+            'text': '_test_policy_atomic_group'
+        },
+        'doc_policy_in_atomic_group': {
+            'text': '_test_policy_in_atomic_group'
+        }
     }
     self.writer.Init()
 
@@ -1036,6 +1042,65 @@
         '</div>'
         '</root>')
 
+  def testAddPolicySectionWithAtomicGroup(self):
+    # Test if policy details are correctly added to the document.
+    policy = {
+        'name':
+            'PolicyName',
+        'caption':
+            'PolicyCaption',
+        'desc':
+            'PolicyDesc',
+        'type':
+            'string',
+        'supported_on': [{
+            'product': 'chrome',
+            'platforms': ['win', 'mac', 'chrome_os'],
+            'since_version': '7',
+            'until_version': '',
+        }],
+        'features': {
+            'dynamic_refresh': False
+        },
+        'example_value':
+            'False',
+        'atomic_group':
+            'PolicyGroup'
+    }
+    self.writer._AddPolicySection(self.doc_root, policy)
+    self.assertEquals(
+        self.doc_root.toxml(), '<root>'
+        '<div style="margin-left: 0px">'
+        '<h3><a name="PolicyName"/>PolicyName</h3>'
+        '<span>PolicyCaption</span>'
+        '<dl>'
+        '<dt style="style_dt;">_test_data_type</dt>'
+        '<dd>String [Windows:REG_SZ]</dd>'
+        '<dt style="style_dt;">_test_win_reg_loc</dt>'
+        '<dd style="style_.monospace;">MockKey\\PolicyName</dd>'
+        '<dt style="style_dt;">_test_chrome_os_reg_loc</dt>'
+        '<dd style="style_.monospace;">MockKeyCrOS\\PolicyName</dd>'
+        '<dt style="style_dt;">_test_mac_linux_pref_name</dt>'
+        '<dd style="style_.monospace;">PolicyName</dd>'
+        '<dt style="style_dt;">_test_supported_on</dt>'
+        '<dd>'
+        '<ul style="style_ul;">'
+        '<li>Chrome (Windows, Mac, Chrome OS) ..7..</li>'
+        '</ul>'
+        '</dd>'
+        '<dt style="style_dt;">_test_supported_features</dt>'
+        '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>'
+        '<dt style="style_dt;">_test_description</dt>'
+        '<dd><p>PolicyDesc</p></dd>'
+        '<dt style="style_dt;">_test_example_value</dt>'
+        '<dd>&quot;False&quot;</dd>'
+        '<dt style="style_dt;">_test_policy_atomic_group</dt>'
+        '<dd>_test_policy_in_atomic_group <a href="./policy-list-3/atomic_groups#PolicyGroup">PolicyGroup</a></dd>'
+        '</dl>'
+        '<a href="#top">_test_back_to_top</a>'
+        '</div>'
+        '</root>')
+
   def testAddPolicySectionForWindowsOnly(self):
     policy = {
         'name':
diff --git a/components/policy/tools/template_writers/writers/json_writer_unittest.py b/components/policy/tools/template_writers/writers/json_writer_unittest.py
index 8f9e36fa..1f712787 100755
--- a/components/policy/tools/template_writers/writers/json_writer_unittest.py
+++ b/components/policy/tools/template_writers/writers/json_writer_unittest.py
@@ -62,6 +62,7 @@
     policy_json = '''
         {
           "policy_definitions": [],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -74,6 +75,7 @@
     policy_json = '''
         {
           "policy_definitions": [],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -98,6 +100,7 @@
               "example_value": True
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -127,6 +130,7 @@
               "example_value": True
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -156,6 +160,7 @@
               "example_value": "hello, world!"
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -181,6 +186,7 @@
               "example_value": 15
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -210,6 +216,7 @@
               "example_value": 1
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -241,6 +248,7 @@
               "example_value": "one"
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -266,6 +274,7 @@
               "example_value": ["foo", "bar"]
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -297,6 +306,7 @@
               "example_value": ["one", "two"]
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -332,6 +342,7 @@
               "example_value": ''' + str(example) + '''
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": %s,
         }''' % MESSAGES
@@ -364,6 +375,7 @@
               "example_value": %s
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": %s,
         }''' % (str(example), MESSAGES)
@@ -392,6 +404,7 @@
               "example_value": ["a"]
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -428,6 +441,7 @@
               "example_value": "c"
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
diff --git a/components/policy/tools/template_writers/writers/plist_strings_writer_unittest.py b/components/policy/tools/template_writers/writers/plist_strings_writer_unittest.py
index b95a8872..83bdf789 100755
--- a/components/policy/tools/template_writers/writers/plist_strings_writer_unittest.py
+++ b/components/policy/tools/template_writers/writers/plist_strings_writer_unittest.py
@@ -22,6 +22,7 @@
     policy_json = '''
       {
         'policy_definitions': [],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {
           'mac_chrome_preferences': {
@@ -43,6 +44,7 @@
     policy_json = '''
       {
         'policy_definitions': [],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {
           'mac_chrome_preferences': {
@@ -82,6 +84,7 @@
             'desc': 'Description of main policy.',
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {
           'mac_chrome_preferences': {
@@ -124,6 +127,7 @@
             'supported_on': ['chrome.mac:8-'],
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {
           'mac_chrome_preferences': {
@@ -168,6 +172,7 @@
             'supported_on': ['chrome.mac:8-'],
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {
           'mac_chrome_preferences': {
@@ -224,6 +229,7 @@
             'supported_on': ['chrome.mac:8-'],
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {
           'mac_chrome_preferences': {
@@ -276,6 +282,7 @@
             'supported_on': ['chrome.mac:8-'],
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {
           'mac_chrome_preferences': {
@@ -329,6 +336,7 @@
             'supported_on': ['chrome.mac:8-'],
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {
           'mac_chrome_preferences': {
@@ -371,6 +379,7 @@
             'supported_on': ['chrome_os:8-'],
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {
           'mac_chrome_preferences': {
diff --git a/components/policy/tools/template_writers/writers/plist_writer_unittest.py b/components/policy/tools/template_writers/writers/plist_writer_unittest.py
index c2c2ea7..05f7f6c9 100755
--- a/components/policy/tools/template_writers/writers/plist_writer_unittest.py
+++ b/components/policy/tools/template_writers/writers/plist_writer_unittest.py
@@ -90,6 +90,7 @@
     policy_json = '''
       {
         'policy_definitions': [],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {},
       }'''
@@ -107,6 +108,7 @@
     policy_json = '''
       {
         'policy_definitions': [],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {},
       }'''
@@ -142,6 +144,7 @@
             'supported_on': ['chrome.mac:8-'],
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {}
       }'''
@@ -191,6 +194,7 @@
             'supported_on': ['chrome.mac:8-'],
            },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {}
       }'''
@@ -242,6 +246,7 @@
             'supported_on': ['chrome.mac:8-'],
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {}
       }'''
@@ -288,6 +293,7 @@
             'caption': '',
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {},
       }'''
@@ -338,6 +344,7 @@
             'caption': '',
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {},
       }'''
@@ -400,6 +407,7 @@
             'caption': '',
           }
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {},
       }'''
@@ -453,6 +461,7 @@
             'supported_on': ['chrome.mac:8-'],
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {},
       }'''
@@ -503,6 +512,7 @@
             'supported_on': ['chrome.mac:8-'],
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {},
       }'''
@@ -558,6 +568,7 @@
             'supported_on': ['chrome.mac:8-'],
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {},
       }'''
@@ -609,6 +620,7 @@
             'caption': '',
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {},
       }'''
@@ -655,6 +667,7 @@
             'caption': '',
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {},
       }'''
@@ -702,6 +715,7 @@
             'desc': '',
           },
         ],
+        'policy_atomic_group_definitions': [],
         'placeholders': [],
         'messages': {},
       }'''
diff --git a/components/policy/tools/template_writers/writers/reg_writer_unittest.py b/components/policy/tools/template_writers/writers/reg_writer_unittest.py
index 66f0be04..e59777f 100755
--- a/components/policy/tools/template_writers/writers/reg_writer_unittest.py
+++ b/components/policy/tools/template_writers/writers/reg_writer_unittest.py
@@ -36,6 +36,7 @@
     policy_json = '''
         {
           "policy_definitions": [],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {}
         }'''
@@ -50,6 +51,7 @@
     policy_json = '''
         {
           "policy_definitions": [],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {}
         }'''
@@ -76,6 +78,7 @@
               "example_value": True
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -107,6 +110,7 @@
               "example_value": True
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -132,6 +136,7 @@
               "example_value": "hello, world! \\\" \\\\"
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -157,6 +162,7 @@
               "example_value": 26
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -186,6 +192,7 @@
               "example_value": 1
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -215,6 +222,7 @@
               "example_value": "two"
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -240,6 +248,7 @@
               "example_value": ["foo", "bar"]
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -268,6 +277,7 @@
               "example_value": ["foo", "bar"]
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -302,6 +312,7 @@
               "example_value": ''' + str(example) + '''
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -333,6 +344,7 @@
               "example_value": %s
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }''' % str(example)
@@ -360,6 +372,7 @@
               "example_value": ["a"]
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
@@ -397,6 +410,7 @@
               "example_value": "c"
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {},
         }'''
diff --git a/tools/grit/grit/format/policy_templates_json_unittest.py b/tools/grit/grit/format/policy_templates_json_unittest.py
index a738654..2c260fa 100755
--- a/tools/grit/grit/format/policy_templates_json_unittest.py
+++ b/tools/grit/grit/format/policy_templates_json_unittest.py
@@ -70,6 +70,7 @@
               'desc': '''This policy does stuff.'''
             },
           ],
+          "policy_atomic_group_definitions": [],
           "placeholders": [],
           "messages": {
             'message_string_id': {
@@ -173,6 +174,8 @@
       },
     },
   ],
+  'policy_atomic_group_definitions': [
+  ],
   'messages': {
       'message_string_id': {
         'text': '''%s'''
diff --git a/tools/grit/grit/gather/policy_json.py b/tools/grit/grit/gather/policy_json.py
index 2fe3f02d..10a176ce 100644
--- a/tools/grit/grit/gather/policy_json.py
+++ b/tools/grit/grit/gather/policy_json.py
@@ -269,6 +269,11 @@
     self._AddNontranslateableChunk("  'policy_definitions': [\n")
     self._AddItems(self.data['policy_definitions'], 'policy', None, 2)
     self._AddNontranslateableChunk("  ],\n")
+    self._AddNontranslateableChunk("  'policy_atomic_group_definitions': [\n")
+    if 'policy_atomic_group_definitions' in self.data:
+      self._AddItems(self.data['policy_atomic_group_definitions'],
+                    'policy', None, 2)
+    self._AddNontranslateableChunk("  ],\n")
     self._AddMessages()
     self._AddNontranslateableChunk('\n}')
 
diff --git a/tools/grit/grit/gather/policy_json_unittest.py b/tools/grit/grit/gather/policy_json_unittest.py
index 58bfcbf..b50ff9b 100755
--- a/tools/grit/grit/gather/policy_json_unittest.py
+++ b/tools/grit/grit/gather/policy_json_unittest.py
@@ -25,7 +25,11 @@
     return expected
 
   def testEmpty(self):
-    original = "{'policy_definitions': [], 'messages': {}}"
+    original = """{
+      'policy_definitions': [],
+      'policy_atomic_group_definitions': [],
+      'messages': {}
+      }"""
     gatherer = policy_json.PolicyJson(StringIO.StringIO(original))
     gatherer.Parse()
     self.failUnless(len(gatherer.GetCliques()) == 0)
@@ -46,6 +50,7 @@
         "      'label': 'nothing special 3',"
         "    },"
         "  ],"
+        "  'policy_atomic_group_definitions': [],"
         "  'messages': {"
         "    'msg_identifier': {"
         "      'text': 'nothing special 3',"
@@ -73,6 +78,7 @@
         "      ]"
         "    },"
         "  ],"
+        "  'policy_atomic_group_definitions': [],"
         "  'messages': {}"
         "}")
     gatherer = policy_json.PolicyJson(StringIO.StringIO(original))
@@ -102,6 +108,7 @@
                 "      'caption': 'nothing special',"
                 "    },"
                 "  ],"
+                "  'policy_atomic_group_definitions': [],"
                 "  'messages': {}"
                 "}")
     gatherer = policy_json.PolicyJson(StringIO.StringIO(original))
@@ -124,6 +131,7 @@
                 "      },"
                 "    },"
                 "  ],"
+                "  'policy_atomic_group_definitions': [],"
                 "  'messages': {}"
                 "}")
     gatherer = policy_json.PolicyJson(StringIO.StringIO(original))
@@ -146,6 +154,7 @@
                 "      },"
                 "    },"
                 "  ],"
+                "  'policy_atomic_group_definitions': [],"
                 "  'messages': {}"
                 "}")
     gatherer = policy_json.PolicyJson(StringIO.StringIO(original))
@@ -169,6 +178,7 @@
         "      ]"
         "    }"
         "  ],"
+        "  'policy_atomic_group_definitions': [],"
         "  'messages': {}"
         "}")
     gatherer = policy_json.PolicyJson(StringIO.StringIO(original))
@@ -190,6 +200,7 @@
         "      'caption': 'nothing special',"
         "    }"
         "  ],"
+        "  'policy_atomic_group_definitions': [],"
         "  'messages': {}"
         "}")
     gatherer = policy_json.PolicyJson(StringIO.StringIO(original))
@@ -201,6 +212,7 @@
   def testEscapingAndLineBreaks(self):
     original = """{
         'policy_definitions': [],
+        'policy_atomic_group_definitions': [],
         'messages': {
           'msg1': {
             # The following line will contain two backslash characters when it
@@ -250,6 +262,7 @@
                 <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>.''',
           },
         ],
+        'policy_atomic_group_definitions': [],
         'messages': {}
 }"""
     gatherer = policy_json.PolicyJson(StringIO.StringIO(original))