Move flags_ui resources and constants to //components.

TBR=jochen
NOPRESUBMIT=true

Review URL: https://codereview.chromium.org/1354153002

Cr-Commit-Position: refs/heads/master@{#350502}
diff --git a/components/components.gyp b/components/components.gyp
index 0eb812e..4838a70 100644
--- a/components/components.gyp
+++ b/components/components.gyp
@@ -33,6 +33,7 @@
     'error_page.gypi',
     'favicon.gypi',
     'favicon_base.gypi',
+    'flags_ui.gypi',
     'gcm_driver.gypi',
     'google.gypi',
     'guest_view.gypi',
diff --git a/components/components_chromium_strings.grd b/components/components_chromium_strings.grd
index 9a54f5d2..95622df 100644
--- a/components/components_chromium_strings.grd
+++ b/components/components_chromium_strings.grd
@@ -153,6 +153,18 @@
           again for improved performance.
         </message>
       </if>
+
+      <!-- About Flags UI -->
+      <if expr="not chromeos">
+        <message name="IDS_FLAGS_UI_RELAUNCH_NOTICE" desc="Notifies the user that he needs to relaunch Chromium. Shown next to a button that says 'Relaunch Now'.">
+          Your changes will take effect the next time you relaunch Chromium.
+        </message>
+      </if>
+      <if expr="chromeos">
+        <message name="IDS_FLAGS_UI_RELAUNCH_NOTICE" desc="Notifies the user that he needs to restart Chromium OS. Shown next to a button that says 'Restart Now'.">
+          Your changes will take effect the next time you restart your device.
+        </message>
+      </if>
     </messages>
   </release>
 </grit>
diff --git a/components/components_google_chrome_strings.grd b/components/components_google_chrome_strings.grd
index 2721b00..2076a56 100644
--- a/components/components_google_chrome_strings.grd
+++ b/components/components_google_chrome_strings.grd
@@ -153,6 +153,19 @@
           again for improved performance.
         </message>
       </if>
+
+      <!-- About Flags UI -->
+      <if expr="not chromeos">
+        <message name="IDS_FLAGS_UI_RELAUNCH_NOTICE" desc="Notifies the user that he needs to relaunch Chrome. Shown next to a button that says 'Relaunch Now'.">
+          Your changes will take effect the next time you relaunch Google Chrome.
+        </message>
+      </if>
+      <if expr="chromeos">
+        <message name="IDS_FLAGS_UI_RELAUNCH_NOTICE" desc="Notifies the user that he needs to restart Chrome OS. Shown next to a button that says 'Restart Now'.">
+          Your changes will take effect the next time you restart your device.
+        </message>
+      </if>
+
     </messages>
   </release>
 </grit>
diff --git a/components/components_strings.grd b/components/components_strings.grd
index f5ce76c2..f71b63d 100644
--- a/components/components_strings.grd
+++ b/components/components_strings.grd
@@ -138,6 +138,7 @@
       <part file="data_reduction_proxy_strings.grdp" />
       <part file="dom_distiller_strings.grdp" />
       <part file="error_page_strings.grdp" />
+      <part file="flags_ui_strings.grdp" />
       <part file="omnibox_strings.grdp" />
       <part file="password_manager_strings.grdp" />
       <part file="pdf_strings.grdp" />
diff --git a/components/flags_ui.gypi b/components/flags_ui.gypi
new file mode 100644
index 0000000..8e47f81
--- /dev/null
+++ b/components/flags_ui.gypi
@@ -0,0 +1,21 @@
+# Copyright 2015 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.
+
+{
+  'targets': [
+    {
+      # GN version: //components/flags_ui
+      'target_name': 'flags_ui',
+      'type': 'static_library',
+      'include_dirs': [
+        '..',
+      ],
+      'sources': [
+        # Note: sources list duplicated in GN build.
+        'flags_ui/flags_ui_constants.cc',
+        'flags_ui/flags_ui_constants.h',
+      ],
+    },
+  ],
+}
diff --git a/components/flags_ui/BUILD.gn b/components/flags_ui/BUILD.gn
new file mode 100644
index 0000000..7f8e6d5
--- /dev/null
+++ b/components/flags_ui/BUILD.gn
@@ -0,0 +1,10 @@
+# Copyright 2015 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.
+
+source_set("flags_ui") {
+  sources = [
+    "flags_ui_constants.cc",
+    "flags_ui_constants.h",
+  ]
+}
diff --git a/components/flags_ui/OWNERS b/components/flags_ui/OWNERS
new file mode 100644
index 0000000..da5824440
--- /dev/null
+++ b/components/flags_ui/OWNERS
@@ -0,0 +1,7 @@
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/components/flags_ui/flags_ui_constants.cc b/components/flags_ui/flags_ui_constants.cc
new file mode 100644
index 0000000..66a52a6
--- /dev/null
+++ b/components/flags_ui/flags_ui_constants.cc
@@ -0,0 +1,43 @@
+// Copyright 2015 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.
+
+#include "components/flags_ui/flags_ui_constants.h"
+
+namespace flags_ui {
+
+// Resource paths.
+const char kFlagsJS[] = "flags.js";
+
+// Message handlers.
+const char kEnableFlagsExperiment[] = "enableFlagsExperiment";
+const char kRequestFlagsExperiments[] = "requestFlagsExperiments";
+const char kResetAllFlags[] = "resetAllFlags";
+const char kRestartBrowser[] = "restartBrowser";
+
+// Other values.
+const char kChannelPromoBeta[] = "channelPromoBeta";
+const char kChannelPromoDev[] = "channelPromoDev";
+const char kDisable[] = "disable";
+const char kEnable[] = "enable";
+const char kFlagsBlurb[] = "flagsBlurb";
+const char kFlagsLongTitle[] = "flagsLongTitle";
+const char kFlagsNoExperimentsAvailable[] = "flagsNoExperimentsAvailable";
+const char kFlagsNotSupported[] = "flagsNotSupported";
+const char kFlagsNoUnsupportedExperiments[] = "flagsNoUnsupportedExperiments";
+const char kFlagsRestartButton[] = "flagsRestartButton";
+const char kFlagsRestartNotice[] = "flagsRestartNotice";
+const char kFlagsTableTitle[] = "flagsTableTitle";
+const char kFlagsUnsupportedTableTitle[] = "flagsUnsupportedTableTitle";
+const char kFlagsWarningHeader[] = "flagsWarningHeader";
+const char kNeedsRestart[] = "needsRestart";
+const char kOwnerWarning[] = "ownerWarning";
+const char kResetAllButton[] = "resetAllButton";
+const char kReturnFlagsExperiments[] = "returnFlagsExperiments";
+const char kShowBetaChannelPromotion[] = "showBetaChannelPromotion";
+const char kShowDevChannelPromotion[] = "showDevChannelPromotion";
+const char kShowOwnerWarning[] = "showOwnerWarning";
+const char kSupportedExperiments[] = "supportedExperiments";
+const char kUnsupportedExperiments[] = "unsupportedExperiments";
+
+}  // namespace flags_ui
diff --git a/components/flags_ui/flags_ui_constants.h b/components/flags_ui/flags_ui_constants.h
new file mode 100644
index 0000000..7b0f6141
--- /dev/null
+++ b/components/flags_ui/flags_ui_constants.h
@@ -0,0 +1,49 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_FLAGS_UI_FLAGS_UI_CONSTANTS_H_
+#define COMPONENTS_FLAGS_UI_FLAGS_UI_CONSTANTS_H_
+
+namespace flags_ui {
+
+// Resource paths.
+// Must match the resource file names.
+extern const char kFlagsJS[];
+
+// Message handlers.
+// Must match the constants used in the resource files.
+extern const char kEnableFlagsExperiment[];
+extern const char kRequestFlagsExperiments[];
+extern const char kResetAllFlags[];
+extern const char kRestartBrowser[];
+
+// Other values.
+// Must match the constants used in the resource files.
+extern const char kChannelPromoBeta[];
+extern const char kChannelPromoDev[];
+extern const char kDisable[];
+extern const char kEnable[];
+extern const char kFlagsBlurb[];
+extern const char kFlagsLongTitle[];
+extern const char kFlagsNoExperimentsAvailable[];
+extern const char kFlagsNotSupported[];
+extern const char kFlagsNoUnsupportedExperiments[];
+extern const char kFlagsRestartButton[];
+extern const char kFlagsRestartNotice[];
+extern const char kFlagsTableTitle[];
+extern const char kFlagsUnsupportedTableTitle[];
+extern const char kFlagsWarningHeader[];
+extern const char kNeedsRestart[];
+extern const char kOwnerWarning[];
+extern const char kResetAllButton[];
+extern const char kReturnFlagsExperiments[];
+extern const char kShowBetaChannelPromotion[];
+extern const char kShowDevChannelPromotion[];
+extern const char kShowOwnerWarning[];
+extern const char kSupportedExperiments[];
+extern const char kUnsupportedExperiments[];
+
+}  // namespace flags_ui
+
+#endif  // COMPONENTS_FLAGS_UI_FLAGS_UI_CONSTANTS_H_
diff --git a/components/flags_ui/resources/flags.css b/components/flags_ui/resources/flags.css
new file mode 100644
index 0000000..f033ad3
--- /dev/null
+++ b/components/flags_ui/resources/flags.css
@@ -0,0 +1,194 @@
+/* Copyright (c) 2012 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. */
+
+body {
+  margin: 10px 10px 0;
+<if expr="not is_android and not is_ios">
+  min-width: 47em;
+</if>
+  /* Should be larger than the evaluated height of needs-restart. */
+  padding-bottom: 100px;
+}
+
+a {
+  color: blue;
+  font-size: 103%;
+}
+
+.permalink {
+  color: #A0A0A0;
+}
+
+#header {
+  -webkit-padding-start: 55px;
+  background: url(../../../ui/webui/resources/images/hazard.svg)
+      left center / 48px no-repeat;
+  line-height: 48px;
+  margin-bottom: 1.05em;
+}
+
+#title-spacer {
+  display: table-cell;
+  vertical-align: middle;
+}
+
+html[dir=rtl] #header {
+  background-position: right center;
+}
+
+h1 {
+  font-size: 156%;
+  font-weight: bold;
+  margin: 0;
+  padding: 0;
+}
+
+.blurb-container {
+  font-size: 120%;
+  padding-bottom: 1.5em;
+}
+
+#blurb-warning {
+  color: red;
+  font-weight: bold;
+}
+
+div.content {
+  font-size: 88%;
+  margin: 5px auto 10px;
+}
+
+div.content:last-of-type {
+  margin-bottom: 0;
+}
+
+.section-header {
+  background: rgb(235, 239, 249);
+  border-top: 1px solid rgb(181, 199, 222);
+  font-size: 99%;
+  padding: 2px 5px 3px;
+  width: 100%;
+}
+
+.section-header > table tr td:first-child {
+  width: 100%;
+}
+
+.section-header > table {
+  width: 100%;
+}
+
+.section-header-title {
+  font-weight: bold;
+  line-height: 200%;
+}
+
+#experiment-reset-all {
+  float: right;
+}
+
+html[dir=rtl] #experiment-reset-all {
+  float: left;
+}
+
+.vbox-container {
+  -webkit-box-orient: vertical;
+  display: -webkit-box;
+}
+
+.wbox {
+  -webkit-box-align: stretch;
+  -webkit-box-flex: 1;
+  display: -webkit-box;
+}
+
+#top {
+  -webkit-padding-end: 5px;
+}
+
+/* Default and unsupported experiments display grey text on a grey background.
+   The title, however, should remain legible. */
+
+.experiment-unsupported > td,
+.experiment-default > td {
+  background: #F0F0F0;
+  color: #A0A0A0;
+}
+
+.experiment-unsupported .experiment-name,
+.experiment-default .experiment-name {
+  color: #000;
+}
+
+.experiment {
+  border-bottom: 1px solid #cdcdcd;
+}
+
+.experiment td {
+  padding-bottom: 4px;
+  padding-top: 5px;
+}
+
+/* Indent the text related to each experiment. */
+.experiment-text {
+  -webkit-padding-start: 5px;
+}
+
+.experiment-name {
+  font-weight: bold;
+}
+
+.referenced .experiment-name {
+  background-color: rgb(255, 255, 0);
+}
+
+.no-experiments {
+  font-size: 1.2em;
+  margin: 6em 0;
+  text-align: center;
+}
+
+/* Match the indentation of .experiment-text. */
+.experiment-actions {
+  -webkit-padding-start: 5px;
+  margin-bottom: 0.2em;
+  margin-top: 0.2em;
+}
+
+div.needs-restart {
+  /* If you modify properties that change the height of this,
+   * update body.padding-bottom. */
+  background: #FFF;
+  border-top: 1px solid rgb(181, 199, 222);
+  bottom: 0;
+  box-shadow: 0 -2px 2px #ddd;
+  box-sizing: border-box;
+  left: 0;
+  padding-bottom: 15px;
+  padding-left: 15px;
+  padding-right: 15px;
+  padding-top: 15px;
+  position: fixed;
+  width: 100%;
+}
+
+.experiment-restart-button {
+  -webkit-user-select: none;
+  background: rgb(76, 142, 250);
+  border: 0;
+  border-radius: 2px;
+  box-sizing: border-box;
+  color: #fff;
+  cursor: pointer;
+  font-weight: 700;
+  margin-top: 10px;
+  padding: 10px 24px;
+  text-transform: uppercase;
+  transition: box-shadow 200ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+button {
+  font-size: 104%;
+}
+
diff --git a/components/flags_ui/resources/flags.html b/components/flags_ui/resources/flags.html
new file mode 100644
index 0000000..3a6e57a
--- /dev/null
+++ b/components/flags_ui/resources/flags.html
@@ -0,0 +1,178 @@
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
+<head>
+<meta charset="utf-8">
+<if expr="is_android">
+<meta name="viewport" content="width=device-width, user-scalable=no">
+</if>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
+<link rel="stylesheet" href="flags.css">
+<script src="chrome://resources/js/load_time_data.js"></script>
+<script src="chrome://resources/js/util.js"></script>
+<script src="chrome://flags/flags.js"></script>
+<script src="chrome://flags/strings.js"></script>
+</head>
+<body>
+<div id="body-container" style="visibility:hidden">
+
+  <div id="header">
+    <div id="title-spacer"><h1 i18n-content="flagsLongTitle"></h1></div>
+  </div>
+
+  <div class="blurb-container">
+    <span id="blurb-warning" i18n-content="flagsWarningHeader">WARNING</span>
+    <span i18n-content="flagsBlurb">WARNING TEXT</span>
+    <span id="channel-promo-beta" i18n-content="channelPromoBeta" hidden></span>
+    <span id="channel-promo-dev" i18n-content="channelPromoDev" hidden></span>
+  </div>
+
+<if expr="chromeos">
+  <div class="blurb-container" id="owner-warning">
+    <span i18n-content="ownerWarning"></span>
+  </div>
+</if>
+
+  <div id="flagsExperimentTemplate">
+    <div id="container" class="vbox-container">
+    <div id="top" class="wbox">
+      <div class="section-header">
+        <table cellpadding="0" cellspacing="0"><tr valign="center">
+          <td>
+            <span class="section-header-title" i18n-content="flagsTableTitle"
+              >TITLE</span>
+            <button id="experiment-reset-all" type="button"
+                i18n-content="resetAllButton"></button>
+          </td>
+        </tr></table>
+      </div>
+    </div>
+    </div>
+
+    <div class="content">
+      <div class="experiment-name no-experiments"
+           jsdisplay="supportedExperiments.length == 0">
+        <div i18n-content="flagsNoExperimentsAvailable"
+          >NO_EXPERIMENTS_ARE_AVAILABLE</div>
+      </div>
+
+      <div jsdisplay="supportedExperiments.length > 0">
+      <div class="experiment"
+           jsselect="supportedExperiments"
+           jsvalues="id:internal_name">
+        <table width="100%" cellpadding="2" cellspacing="0">
+        <!-- TODO(mkwst): This doesn't work exactly as expected for multivalue
+                          experiments.  See http://crbug.com/73730 -->
+        <tr jsvalues="class: is_default ? 'experiment-default'
+                                        : 'experiment-switched'">
+        <td valign="top">
+          <div class="experiment-text">
+            <div>
+              <span class="experiment-name"
+                    jscontent="name">NAME</span>
+              <span jscontent="supported_platforms.join(', ')"></span>
+              <div>
+                <span jsvalues=".innerHTML:description"></span>
+                <a class="permalink"
+                   jsvalues="href: '#' + internal_name"
+                   jscontent="'#' + internal_name"></a>
+              </div>
+              <div jsdisplay="choices && choices.length > 0">
+                <select
+                  class="experiment-select"
+                  jsvalues=".internal_name:internal_name;.disabled:!enabled">
+                  <option jsvalues=".selected:selected"
+                          jsselect="choices"
+                          jscontent="description">NAME
+                  </option>
+                </select>
+              </div>
+            </div>
+          </div>
+          <div class="experiment-actions">
+            <span>
+              <a
+                class="experiment-disable-link"
+                jsvalues=".internal_name:internal_name"
+                jsdisplay="enabled"
+                href="#"
+                i18n-content="disable"
+                >DISABLE</a>
+              <a
+                class="experiment-enable-link"
+                jsvalues=".internal_name:internal_name"
+                jsdisplay="!enabled"
+                href="#"
+                i18n-content="enable"
+                >ENABLE</a>
+            </span>
+          </div>
+        </td>
+        </tr>
+        </table>
+      </div>
+      </div>
+    </div>
+
+    <div id="container" class="vbox-container">
+    <div id="top" class="wbox">
+      <div class="section-header">
+        <table cellpadding="0" cellspacing="0"><tr valign="center">
+          <td>
+            <span class="section-header-title"
+                  i18n-content="flagsUnsupportedTableTitle"
+              >TITLE
+            </span>
+          </td>
+        </tr></table>
+      </div>
+    </div>
+    </div>
+
+    <div class="content">
+      <div class="experiment-name no-experiments"
+           jsdisplay="unsupportedExperiments.length == 0">
+        <div i18n-content="flagsNoUnsupportedExperiments"
+          >NO_UNSUPPORTED_EXPERIMENTS</div>
+      </div>
+
+      <div jsdisplay="unsupportedExperiments.length > 0">
+      <div class="experiment"
+           jsselect="unsupportedExperiments"
+           jsvalues="id:internal_name">
+        <table width="100%" cellpadding="2" cellspacing="0">
+        <tr class="experiment-unsupported">
+        <td valign="top">
+          <div class="experiment-text">
+            <div>
+              <span class="experiment-name"
+                    jscontent="name">NAME</span>
+              <span jscontent="supported_platforms.join(', ')"></span>
+              <div>
+                <span jsvalues=".innerHTML:description"></span>
+                <a class="permalink"
+                   jsvalues="href: '#' + internal_name"
+                   jscontent="'#' + internal_name"></a>
+              </div>
+            </div>
+          </div>
+          <div class="experiment-actions">
+            <div i18n-content="flagsNotSupported"></div>
+          </div>
+        </td>
+        </tr>
+        </table>
+      </div>
+      </div>
+    </div>
+
+    <div class="needs-restart" jsdisplay="needsRestart">
+      <div i18n-content="flagsRestartNotice">NEEDS_RESTART</div>
+      <button class="experiment-restart-button" type="button"
+              i18n-content="flagsRestartButton">RESTART</button>
+    </div>
+  </div>
+</div>
+<script src="chrome://resources/js/i18n_template.js"></script>
+<script src="chrome://resources/js/jstemplate_compiled.js"></script>
+</body>
+</html>
diff --git a/components/flags_ui/resources/flags.js b/components/flags_ui/resources/flags.js
new file mode 100644
index 0000000..2ef351f
--- /dev/null
+++ b/components/flags_ui/resources/flags.js
@@ -0,0 +1,186 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * This variable structure is here to document the structure that the template
+ * expects to correctly populate the page.
+ */
+
+/**
+ * Takes the |experimentsData| input argument which represents data about all
+ * the current experiments and populates the html jstemplate with that data.
+ * with that data. It expects an object structure like the above.
+ * @param {Object} experimentsData Information about all experiments.
+ *     See returnFlagsExperiments() for the structure of this object.
+ */
+function renderTemplate(experimentsData) {
+  // This is the javascript code that processes the template:
+  jstProcess(new JsEvalContext(experimentsData),
+             $('flagsExperimentTemplate'));
+
+  // Add handlers to dynamically created HTML elements.
+  var elements = document.getElementsByClassName('experiment-select');
+  for (var i = 0; i < elements.length; ++i) {
+    elements[i].onchange = function() {
+      handleSelectChoiceExperiment(this, this.selectedIndex);
+      return false;
+    };
+  }
+
+  elements = document.getElementsByClassName('experiment-disable-link');
+  for (var i = 0; i < elements.length; ++i) {
+    elements[i].onclick = function() {
+      handleEnableExperiment(this, false);
+      return false;
+    };
+  }
+
+  elements = document.getElementsByClassName('experiment-enable-link');
+  for (var i = 0; i < elements.length; ++i) {
+    elements[i].onclick = function() {
+      handleEnableExperiment(this, true);
+      return false;
+    };
+  }
+
+  elements = document.getElementsByClassName('experiment-restart-button');
+  for (var i = 0; i < elements.length; ++i) {
+    elements[i].onclick = restartBrowser;
+  }
+
+  $('experiment-reset-all').onclick = resetAllFlags;
+
+  highlightReferencedFlag();
+}
+
+/**
+ * Highlight an element associated with the page's location's hash. We need to
+ * fake fragment navigation with '.scrollIntoView()', since the fragment IDs
+ * don't actually exist until after the template code runs; normal navigation
+ * therefore doesn't work.
+ */
+function highlightReferencedFlag() {
+  if (window.location.hash) {
+    var el = document.querySelector(window.location.hash);
+    if (el && !el.classList.contains('referenced')) {
+      // Unhighlight whatever's highlighted.
+      if (document.querySelector('.referenced'))
+        document.querySelector('.referenced').classList.remove('referenced');
+      // Highlight the referenced element.
+      el.classList.add('referenced');
+      el.scrollIntoView();
+    }
+  }
+}
+
+/**
+ * Asks the C++ FlagsDOMHandler to get details about the available experiments
+ * and return detailed data about the configuration. The FlagsDOMHandler
+ * should reply to returnFlagsExperiments() (below).
+ */
+function requestFlagsExperimentsData() {
+  chrome.send('requestFlagsExperiments');
+}
+
+/**
+ * Asks the C++ FlagsDOMHandler to restart the browser (restoring tabs).
+ */
+function restartBrowser() {
+  chrome.send('restartBrowser');
+}
+
+/**
+ * Reset all flags to their default values and refresh the UI.
+ */
+function resetAllFlags() {
+  // Asks the C++ FlagsDOMHandler to reset all flags to default values.
+  chrome.send('resetAllFlags');
+  requestFlagsExperimentsData();
+}
+
+/**
+ * Called by the WebUI to re-populate the page with data representing the
+ * current state of all experiments.
+ * @param {Object} experimentsData Information about all experiments.
+ *     in the following format:
+ *   {
+ *     supportedExperiments: [
+ *       {
+ *         internal_name: 'Experiment ID string',
+ *         name: 'Experiment Name',
+ *         description: 'description',
+ *         // enabled and default are only set if the experiment is single
+ *         // valued.
+ *         // enabled is true if the experiment is currently enabled.
+ *         // is_default is true if the experiment is in its default state.
+ *         enabled: true,
+ *         is_default: false,
+ *         // choices is only set if the experiment has multiple values.
+ *         choices: [
+ *           {
+ *             internal_name: 'Experiment ID string',
+ *             description: 'description',
+ *             selected: true
+ *           }
+ *         ],
+ *         supported_platforms: [
+ *           'Mac',
+ *           'Linux'
+ *         ],
+ *       }
+ *     ],
+ *     unsupportedExperiments: [
+ *       // Mirrors the format of |supportedExperiments| above.
+ *     ],
+ *     needsRestart: false,
+ *     showBetaChannelPromotion: false,
+ *     showDevChannelPromotion: false,
+ *     showOwnerWarning: false
+ *   }
+ */
+function returnFlagsExperiments(experimentsData) {
+  var bodyContainer = $('body-container');
+  renderTemplate(experimentsData);
+
+  if (experimentsData.showBetaChannelPromotion)
+    $('channel-promo-beta').hidden = false;
+  else if (experimentsData.showDevChannelPromotion)
+    $('channel-promo-dev').hidden = false;
+
+  bodyContainer.style.visibility = 'visible';
+  var ownerWarningDiv = $('owner-warning');
+  if (ownerWarningDiv)
+    ownerWarningDiv.hidden = !experimentsData.showOwnerWarning;
+}
+
+/**
+ * Handles a 'enable' or 'disable' button getting clicked.
+ * @param {HTMLElement} node The node for the experiment being changed.
+ * @param {boolean} enable Whether to enable or disable the experiment.
+ */
+function handleEnableExperiment(node, enable) {
+  // Tell the C++ FlagsDOMHandler to enable/disable the experiment.
+  chrome.send('enableFlagsExperiment', [String(node.internal_name),
+                                        String(enable)]);
+  requestFlagsExperimentsData();
+}
+
+/**
+ * Invoked when the selection of a multi-value choice is changed to the
+ * specified index.
+ * @param {HTMLElement} node The node for the experiment being changed.
+ * @param {number} index The index of the option that was selected.
+ */
+function handleSelectChoiceExperiment(node, index) {
+  // Tell the C++ FlagsDOMHandler to enable the selected choice.
+  chrome.send('enableFlagsExperiment',
+              [String(node.internal_name) + '@' + index, 'true']);
+  requestFlagsExperimentsData();
+}
+
+// Get data and have it displayed upon loading.
+document.addEventListener('DOMContentLoaded', requestFlagsExperimentsData);
+
+// Update the highlighted flag when the hash changes.
+window.addEventListener('hashchange', highlightReferencedFlag);
diff --git a/components/flags_ui_strings.grdp b/components/flags_ui_strings.grdp
new file mode 100644
index 0000000..71b4dd7
--- /dev/null
+++ b/components/flags_ui_strings.grdp
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<grit-part>
+  <message name="IDS_FLAGS_UI_LONG_TITLE" desc="Long version of the title for the about:flags page.">
+    Careful, these experiments may bite
+  </message>
+  <message name="IDS_FLAGS_UI_TABLE_TITLE" desc="Title for the experiments table on the about:flags page.">
+    Experiments
+  </message>
+  <message name="IDS_FLAGS_UI_WARNING_HEADER" desc="Text right before the warning body text.">
+    WARNING
+  </message>
+  <message name="IDS_FLAGS_UI_WARNING_TEXT" desc="Text at the top of the about:flags page, warning users that the about:flags contents are volatile.">
+    These experimental features may change, break, or disappear at any time. We make absolutely no guarantees about what may happen if you turn one of these experiments on, and your browser may even spontaneously combust. Jokes aside, your browser may delete all your data, or your security and privacy could be compromised in unexpected ways. Any experiments you enable will be enabled for all users of this browser. Please proceed with caution.
+  </message>
+  <message name="IDS_FLAGS_UI_PROMOTE_BETA_CHANNEL" desc="Shown on the about:flags page when we want to promote the Chrome beta channel">
+    Interested in cool new Chrome features? Try our beta channel at chrome.com/beta.
+  </message>
+  <message name="IDS_FLAGS_UI_PROMOTE_DEV_CHANNEL" desc="Shown on the about:flags page when we want to promote the Chrome developer channel">
+    Interested in cool new Chrome features? Try our dev channel at chrome.com/dev.
+  </message>
+        <message name="IDS_FLAGS_UI_DISABLE" desc="The link for disabling a labs experiment.">
+    Disable
+  </message>
+  <message name="IDS_FLAGS_UI_ENABLE" desc="The link for enabling a labs experiment.">
+    Enable
+  </message>
+  <message name="IDS_FLAGS_UI_RESET_ALL_BUTTON" desc="Text on a button that resets all experiments to their default state when clicked.">
+    Reset all to default
+  </message>
+  <message name="IDS_FLAGS_UI_NO_EXPERIMENTS_AVAILABLE" desc="Shown if no experiments are available">
+    Aww, it looks like there are currently no experiments available.
+  </message>
+  <message name="IDS_FLAGS_UI_UNSUPPORTED_TABLE_TITLE" desc="Title for the unsupported experiments table on the about:flags page. It lists all experiments that are not available on the current platform.">
+    Unavailable Experiments
+  </message>
+  <message name="IDS_FLAGS_UI_NO_UNSUPPORTED_EXPERIMENTS" desc="Show if there are no unavailable experiments.">
+    All experiments are available on your platform!
+  </message>
+  <message name="IDS_FLAGS_UI_NOT_AVAILABLE" desc="Shown instead of the Enable/Disable link, to let the user know that an experiment is not available on the current platform.">
+    Sorry, this experiment is not available on your platform.
+  </message>
+  <message name="IDS_FLAGS_UI_ENABLE_NACL_NAME" desc="Name of the 'Enable Native Client' lab.">
+    Native Client
+  </message>
+  <if expr="not chromeos">
+    <message name="IDS_FLAGS_UI_RELAUNCH_BUTTON" desc="Text on a button that relaunches Chrome when clicked. ">
+      Relaunch Now
+    </message>
+  </if>
+  <if expr="chromeos">
+    <message name="IDS_FLAGS_UI_RELAUNCH_BUTTON" desc="Text on a button that restarts Chrome OS when clicked. ">
+      Restart Now
+    </message>
+    <message name="IDS_FLAGS_UI_SYSTEM_OWNER_ONLY" desc="A warning that flags that apply system-wide can only be set by the owner.">
+      Flags that apply system-wide can only be set by the owner: <ph name="OWNER_EMAIL">$1<ex>[email protected]</ex></ph>.
+    </message>
+  </if>
+</grit-part>
diff --git a/components/resources/components_resources.grd b/components/resources/components_resources.grd
index 50ef33a4..0adffee 100644
--- a/components/resources/components_resources.grd
+++ b/components/resources/components_resources.grd
@@ -10,6 +10,7 @@
     <includes>
       <part file="data_reduction_proxy_resources.grdp" />
       <part file="dom_distiller_resources.grdp" />
+      <part file="flags_ui_resources.grdp" />
       <part file="gcm_driver_resources.grdp" />
       <part file="printing_resources.grdp" />
       <part file="proximity_auth_resources.grdp" />
diff --git a/components/resources/flags_ui_resources.grdp b/components/resources/flags_ui_resources.grdp
new file mode 100644
index 0000000..f5e599c
--- /dev/null
+++ b/components/resources/flags_ui_resources.grdp
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<grit-part>
+  <include name="IDR_FLAGS_UI_FLAGS_HTML" file="../flags_ui/resources/flags.html" flattenhtml="true" type="BINDATA" />
+  <include name="IDR_FLAGS_UI_FLAGS_JS" file="../flags_ui/resources/flags.js" type="BINDATA" />
+</grit-part>