[go: nahoru, domu]

Add UI to customization caption styling

Allow Chrome users on platform that do not have system caption
styling to provide a default styling for WebVTT captions displayed
by Blink. This should affect Chrome OS and Linux as Windows, macOS
and Android have system settings.

Design doc: https://docs.google.com/document/d/1wgKMbvP8nJkUKXkwnh5iEajSa4GHxDURL8kHIyHEO4s/edit?hl=en#

Bug: 976966
Change-Id: I41282753305b81f76e6b3f565516160cd63d69f9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1656714
Reviewed-by: Avi Drissman <avi@chromium.org>
Reviewed-by: Mounir Lamouri <mlamouri@chromium.org>
Reviewed-by: Esmael El-Moslimany <aee@chromium.org>
Commit-Queue: Evan Liu <evliu@google.com>
Cr-Commit-Position: refs/heads/master@{#672706}
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 92654cb7..e696de8 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -162,6 +162,78 @@
   </if>
 
   <!-- Accessibility Page -->
+  <message name="IDS_SETTINGS_CAPTIONS" desc="Name of the settings page which displays caption preferences.">
+    Captions
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_TEXT_SIZE" desc="Name of the settings page which displays caption text size preferences.">
+    Text size
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_TEXT_FONT" desc="Name of the settings page which displays caption text font preferences.">
+    Text font
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_TEXT_COLOR" desc="Name of the settings page which displays caption text color preferences.">
+    Text color
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_TEXT_OPACITY" desc="Name of the settings page which displays caption text opacity preferences.">
+    Text opacity
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_BACKGROUND_OPACITY" desc="Name of the settings page which displays caption background opacity preferences.">
+    Background opacity
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_OPACITY_MIN" desc="Value of the minimum captions text opacity setting.">
+    0
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_OPACITY_MAX" desc="Value of the maximum captions text opacity setting.">
+    100
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_TEXT_SHADOW" desc="Name of the settings page which displays caption text shadow preferences.">
+    Text shadow
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_TEXT_SHADOW_NONE" desc="Name of the None option for the caption text shadow.">
+    None
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_TEXT_SHADOW_RAISED" desc="Name of the Raised option for the caption text shadow.">
+    Raised
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_TEXT_SHADOW_DEPRESSED" desc="Name of the Depressed option for the caption text shadow.">
+    Depressed
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_TEXT_SHADOW_UNIFORM" desc="Name of the Uniform option for the caption text shadow.">
+    Uniform
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_TEXT_SHADOW_DROP_SHADOW" desc="Name of the Drop shadow option for the caption text shadow.">
+    Drop shadow
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_BACKGROUND_COLOR" desc="Name of the settings page which displays caption background color preferences.">
+    Background color
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_COLOR_BLACK" desc="Name of the Black color for the caption text or background.">
+    Black
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_COLOR_WHITE" desc="Name of the White color for the caption text or background.">
+    White
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_COLOR_RED" desc="Name of the Red color for the caption text or background.">
+    Red
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_COLOR_GREEN" desc="Name of the Green color for the caption text or background.">
+    Green
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_COLOR_BLUE" desc="Name of the Blue color for the caption text or background.">
+    Blue
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_COLOR_YELLOW" desc="Name of the Yellow color for the caption text or background.">
+    Yellow
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_COLOR_CYAN" desc="Name of the Cyan color for the caption text or background.">
+    Cyan
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_COLOR_MAGENTA" desc="Name of the Magenta color for the caption text or background.">
+    Magenta
+  </message>
+  <message name="IDS_SETTINGS_CAPTIONS_DEFAULT_SETTING" desc="Name of the default setting for the caption text.">
+    Default
+  </message>
   <message name="IDS_SETTINGS_ACCESSIBILITY" desc="Name of the settings page which displays accessibility preferences.">
     Accessibility
   </message>
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_CAPTIONS.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_CAPTIONS.png.sha1
new file mode 100644
index 0000000..4ba1991
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_CAPTIONS.png.sha1
@@ -0,0 +1 @@
+3e05aa8562cd7a68d00d313431aca4dfe7effc5e
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 2ca9d3aa..b0b8605 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3840,6 +3840,20 @@
     ]
   }
 
+  if (is_mac) {
+    sources += [
+      "accessibility/caption_settings_dialog.h",
+      "accessibility/caption_settings_dialog_mac.mm",
+    ]
+  }
+
+  if (is_win) {
+    sources += [
+      "accessibility/caption_settings_dialog.h",
+      "accessibility/caption_settings_dialog_win.cc",
+    ]
+  }
+
   if (is_win || is_mac || is_desktop_linux) {
     sources += [
       "browser_switcher/alternative_browser_driver.h",
diff --git a/chrome/browser/accessibility/caption_settings_dialog.h b/chrome/browser/accessibility/caption_settings_dialog.h
new file mode 100644
index 0000000..d6f08fa
--- /dev/null
+++ b/chrome/browser/accessibility/caption_settings_dialog.h
@@ -0,0 +1,25 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ACCESSIBILITY_CAPTION_SETTINGS_DIALOG_H_
+#define CHROME_BROWSER_ACCESSIBILITY_CAPTION_SETTINGS_DIALOG_H_
+
+#include "base/macros.h"
+
+namespace captions {
+
+// An abstraction of a caption settings dialog. This is used for the captions
+// sub-section of Settings.
+class CaptionSettingsDialog {
+ public:
+  // Displays the native captions manager dialog.
+  static void ShowCaptionSettingsDialog();
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(CaptionSettingsDialog);
+};
+
+}  // namespace captions
+
+#endif  // CHROME_BROWSER_ACCESSIBILITY_CAPTION_SETTINGS_DIALOG_H_
diff --git a/chrome/browser/accessibility/caption_settings_dialog_mac.mm b/chrome/browser/accessibility/caption_settings_dialog_mac.mm
new file mode 100644
index 0000000..ae9c4aa
--- /dev/null
+++ b/chrome/browser/accessibility/caption_settings_dialog_mac.mm
@@ -0,0 +1,20 @@
+// 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.
+
+#include "chrome/browser/accessibility/caption_settings_dialog.h"
+
+#import <AppKit/AppKit.h>
+
+namespace captions {
+
+static NSString* kCaptionSettingsUrlString =
+    @"x-apple.systempreferences:com.apple.preference.universalaccess?"
+    @"Captioning";
+
+void CaptionSettingsDialog::ShowCaptionSettingsDialog() {
+  [[NSWorkspace sharedWorkspace]
+      openURL:[NSURL URLWithString:kCaptionSettingsUrlString]];
+}
+
+}  // namespace captions
diff --git a/chrome/browser/accessibility/caption_settings_dialog_win.cc b/chrome/browser/accessibility/caption_settings_dialog_win.cc
new file mode 100644
index 0000000..aef787a
--- /dev/null
+++ b/chrome/browser/accessibility/caption_settings_dialog_win.cc
@@ -0,0 +1,34 @@
+// 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.
+
+#include "chrome/browser/accessibility/caption_settings_dialog.h"
+
+#include <windows.h>
+#include <shellapi.h>
+
+#include "base/bind.h"
+#include "base/task/post_task.h"
+#include "base/win/windows_version.h"
+
+namespace {
+
+// A helper callback that opens the caption settings dialog.
+void CaptionSettingsDialogCallback() {
+  if (base::win::GetVersion() >= base::win::Version::WIN10) {
+    ShellExecute(NULL, L"open", L"ms-settings:easeofaccess-closedcaptioning",
+                 NULL, NULL, SW_SHOWNORMAL);
+  }
+}
+
+}  // namespace
+
+namespace captions {
+
+void CaptionSettingsDialog::ShowCaptionSettingsDialog() {
+  base::PostTaskWithTraits(
+      FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
+      base::BindOnce(CaptionSettingsDialogCallback));
+}
+
+}  // namespace captions
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 5c2288a..0faee1f7 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3154,19 +3154,25 @@
 
   style.text_size = prefs->GetString(prefs::kAccessibilityCaptionsTextSize);
   style.font_family = prefs->GetString(prefs::kAccessibilityCaptionsTextFont);
-  style.text_color = base::StringPrintf(
-      "rgba(%s,%s)",
-      prefs->GetString(prefs::kAccessibilityCaptionsTextColor).c_str(),
-      base::NumberToString(
-          prefs->GetInteger(prefs::kAccessibilityCaptionsTextOpacity) / 100.0)
-          .c_str());
-  style.background_color = base::StringPrintf(
-      "rgba(%s,%s)",
-      prefs->GetString(prefs::kAccessibilityCaptionsBackgroundColor).c_str(),
-      base::NumberToString(
-          prefs->GetInteger(prefs::kAccessibilityCaptionsBackgroundOpacity) /
-          100.0)
-          .c_str());
+  if (!prefs->GetString(prefs::kAccessibilityCaptionsTextColor).empty()) {
+    style.text_color = base::StringPrintf(
+        "rgba(%s,%s)",
+        prefs->GetString(prefs::kAccessibilityCaptionsTextColor).c_str(),
+        base::NumberToString(
+            prefs->GetInteger(prefs::kAccessibilityCaptionsTextOpacity) / 100.0)
+            .c_str());
+  }
+
+  if (!prefs->GetString(prefs::kAccessibilityCaptionsBackgroundColor).empty()) {
+    style.background_color = base::StringPrintf(
+        "rgba(%s,%s)",
+        prefs->GetString(prefs::kAccessibilityCaptionsBackgroundColor).c_str(),
+        base::NumberToString(
+            prefs->GetInteger(prefs::kAccessibilityCaptionsBackgroundOpacity) /
+            100.0)
+            .c_str());
+  }
+
   style.text_shadow = prefs->GetString(prefs::kAccessibilityCaptionsTextShadow);
 
   return style;
diff --git a/chrome/browser/resources/settings/a11y_page/BUILD.gn b/chrome/browser/resources/settings/a11y_page/BUILD.gn
index 0be94d4..cb0f25da 100644
--- a/chrome/browser/resources/settings/a11y_page/BUILD.gn
+++ b/chrome/browser/resources/settings/a11y_page/BUILD.gn
@@ -7,6 +7,8 @@
 js_type_check("closure_compile") {
   deps = [
     ":a11y_page",
+    ":captions_browser_proxy",
+    ":captions_subpage",
   ]
 
   if (is_chromeos) {
@@ -19,6 +21,16 @@
   }
 }
 
+js_library("captions_subpage") {
+  deps = [
+    "../appearance_page:fonts_browser_proxy",
+    "../controls:settings_dropdown_menu",
+    "//ui/webui/resources/cr_elements/cr_slider:cr_slider",
+    "//ui/webui/resources/js:i18n_behavior",
+    "//ui/webui/resources/js:web_ui_listener_behavior",
+  ]
+}
+
 js_library("a11y_page") {
   deps = [
     "..:route",
@@ -61,3 +73,9 @@
 
 js_library("externs") {
 }
+
+js_library("captions_browser_proxy") {
+  deps = [
+    "//ui/webui/resources/js:cr",
+  ]
+}
diff --git a/chrome/browser/resources/settings/a11y_page/a11y_page.html b/chrome/browser/resources/settings/a11y_page/a11y_page.html
index c13d604..a4acf13 100644
--- a/chrome/browser/resources/settings/a11y_page/a11y_page.html
+++ b/chrome/browser/resources/settings/a11y_page/a11y_page.html
@@ -1,5 +1,7 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
+<link rel="import" href="captions_browser_proxy.html">
+<link rel="import" href="captions_subpage.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="../i18n_setup.html">
@@ -17,76 +19,100 @@
 <dom-module id="settings-a11y-page">
   <template>
     <style include="settings-shared"></style>
-<if expr="chromeos">
-    <template is="dom-if" if="[[pageVisibility.webstoreLink]]">
-      <settings-animated-pages id="pages" current-route="{{currentRoute}}"
-          section="a11y" focus-config="[[focusConfig_]]">
+    <settings-animated-pages id="pages" current-route="{{currentRoute}}"
+        section="a11y" focus-config="[[focusConfig_]]">
+      <if expr="not chromeos">
         <div route-path="default">
-          <settings-toggle-button
-              id="a11yImageLabels"
-              hidden$="[[!showAccessibilityLabelsSetting_]]"
-              pref="{{prefs.settings.a11y.enable_accessibility_image_labels}}"
-              on-change="onToggleAccessibilityImageLabels_"
-              label="$i18n{accessibleImageLabelsTitle}"
-              sub-label="$i18n{accessibleImageLabelsSubtitle}">
-          </settings-toggle-button>
-          <settings-toggle-button id="optionsInMenuToggle"
-              label="$i18n{optionsInMenuLabel}"
-              pref="{{prefs.settings.a11y.enable_menu}}">
-          </settings-toggle-button>
-          <cr-link-row class="hr" id="subpage-trigger"
-              label="$i18n{manageAccessibilityFeatures}"
-              on-click="onManageAccessibilityFeaturesTap_"
-              sub-label="$i18n{moreFeaturesLinkDescription}">
-          </cr-link-row>
+          <template is="dom-if" if="[[showCaptionSettings_]]">
+            <cr-link-row class="hr" id="captions" label="$i18n{captionsTitle}"
+                on-click="onTapCaptions_">
+            </cr-link-row>
+          </template>
+          <if expr="not chromeos">
+            <settings-toggle-button id="a11yImageLabels"
+                hidden$="[[!showAccessibilityLabelsSetting_]]"
+                pref="{{prefs.settings.a11y.enable_accessibility_image_labels}}"
+                on-change="onToggleAccessibilityImageLabels_" 
+                label="$i18n{accessibleImageLabelsTitle}"
+                sub-label="$i18n{accessibleImageLabelsSubtitle}">
+            </settings-toggle-button>
+            <cr-link-row class="hr" label="$i18n{moreFeaturesLink}"
+                on-click="onMoreFeaturesLinkClick_"
+                sub-label="$i18n{a11yWebStore}" external></cr-link-row>
+          </if>
         </div>
-
-        <template is="dom-if" route-path="/manageAccessibility">
-          <settings-subpage
-              associated-control="[[$$('#subpage-trigger')]]"
-              page-title="$i18n{manageAccessibilityFeatures}">
-            <settings-manage-a11y-page prefs="{{prefs}}">
-            </settings-manage-a11y-page>
-          </settings-subpage>
-        </template>
-        <template is="dom-if" route-path="/manageAccessibility/tts">
-          <settings-subpage
-              associated-control="[[$$('#subpage-trigger')]]"
-              page-title="$i18n{manageTtsSettings}">
-            <settings-tts-subpage prefs="{{prefs}}">
-            </settings-tts-subpage>
-          </settings-subpage>
-        </template>
-        <template is="dom-if" if="[[showExperimentalSwitchAccess_]]">
-          <template is="dom-if" route-path="/manageAccessibility/switchAccess">
+      </if>
+      <if expr="chromeos">
+        <template is="dom-if" if="[[pageVisibility.webstoreLink]]">
+          <div route-path="default">
+            <template is="dom-if" if="[[showCaptionSettings_]]">
+              <cr-link-row class="hr" id="captions"
+                  label="$i18n{captionsTitle}"
+                  on-click="onTapCaptions_">
+              </cr-link-row>
+            </template>
+            <settings-toggle-button id="a11yImageLabels"
+                hidden$="[[!showAccessibilityLabelsSetting_]]"
+                pref="{{prefs.settings.a11y.enable_accessibility_image_labels}}"
+                on-change="onToggleAccessibilityImageLabels_"
+                label="$i18n{accessibleImageLabelsTitle}"
+                sub-label="$i18n{accessibleImageLabelsSubtitle}">
+            </settings-toggle-button>
+            <settings-toggle-button id="optionsInMenuToggle"
+                label="$i18n{optionsInMenuLabel}"
+                pref="{{prefs.settings.a11y.enable_menu}}">
+            </settings-toggle-button>
+            <cr-link-row class="hr" id="subpage-trigger"
+                label="$i18n{manageAccessibilityFeatures}"
+                on-click="onManageAccessibilityFeaturesTap_"
+                sub-label="$i18n{moreFeaturesLinkDescription}">
+            </cr-link-row>
+          </div>
+          <template is="dom-if" route-path="/manageAccessibility">
             <settings-subpage associated-control="[[$$('#subpage-trigger')]]"
-                page-title="$i18n{manageSwitchAccessSettings}">
-              <settings-switch-access-subpage prefs="{{prefs.settings.a11y}}">
-              </settings-switch-access-subpage>
+                page-title="$i18n{manageAccessibilityFeatures}">
+              <settings-manage-a11y-page prefs="{{prefs}}">
+              </settings-manage-a11y-page>
+            </settings-subpage>
+          </template>
+          <template is="dom-if" route-path="/manageAccessibility/tts">
+            <settings-subpage
+                associated-control="[[$$('#subpage-trigger')]]"
+                page-title="$i18n{manageTtsSettings}">
+              <settings-tts-subpage prefs="{{prefs}}">
+              </settings-tts-subpage>
+            </settings-subpage>
+          </template>
+          <template is="dom-if" if="[[showExperimentalSwitchAccess_]]">
+            <template is="dom-if"
+                route-path="/manageAccessibility/switchAccess">
+              <settings-subpage associated-control="[[$$('#subpage-trigger')]]"
+                  page-title="$i18n{manageSwitchAccessSettings}">
+                <settings-switch-access-subpage prefs="{{prefs.settings.a11y}}">
+                </settings-switch-access-subpage>
+              </settings-subpage>
+            </template>
+          </template>
+        </template>
+        <cr-link-row class="hr"
+            label="$i18n{moreFeaturesLink}"
+            on-click="onMoreFeaturesLinkClick_"
+            sub-label="$i18n{a11yWebStore}"
+            hidden="[[pageVisibility.webstoreLink]]" external></cr-link-row>
+      </if>
+
+      <if expr="chromeos or is_linux or is_win">
+        <template is="dom-if" if="[[showCaptionSettings_]]">
+          <template is="dom-if" route-path="/captions">
+            <settings-subpage
+                associated-control="[[$$('#captions')]]"
+                page-title="$i18n{captionsTitle}">
+              <settings-captions prefs="{{prefs}}"></settings-captions>
             </settings-subpage>
           </template>
         </template>
-      </settings-animated-pages>
-    </template>
-    <cr-link-row class="hr" label="$i18n{moreFeaturesLink}"
-        on-click="onMoreFeaturesLinkClick_" sub-label="$i18n{a11yWebStore}"
-        hidden="[[pageVisibility.webstoreLink]]"
-        external></cr-link-row>
-</if>
-
-<if expr="not chromeos">
-    <settings-toggle-button
-        id="a11yImageLabels"
-        hidden$="[[!showAccessibilityLabelsSetting_]]"
-        pref="{{prefs.settings.a11y.enable_accessibility_image_labels}}"
-        on-change="onToggleAccessibilityImageLabels_"
-        label="$i18n{accessibleImageLabelsTitle}"
-        sub-label="$i18n{accessibleImageLabelsSubtitle}">
-    </settings-toggle-button>
-    <cr-link-row class="hr" label="$i18n{moreFeaturesLink}"
-        on-click="onMoreFeaturesLinkClick_" sub-label="$i18n{a11yWebStore}"
-        external></cr-link-row>
-</if>
+      </if>
+    </settings-animated-pages>
 
   </template>
   <script src="a11y_page.js"></script>
diff --git a/chrome/browser/resources/settings/a11y_page/a11y_page.js b/chrome/browser/resources/settings/a11y_page/a11y_page.js
index 9f0a574..e1cfc426 100644
--- a/chrome/browser/resources/settings/a11y_page/a11y_page.js
+++ b/chrome/browser/resources/settings/a11y_page/a11y_page.js
@@ -43,6 +43,9 @@
       type: Object,
       value: function() {
         const map = new Map();
+        if (settings.routes.CAPTIONS) {
+          map.set(settings.routes.CAPTIONS.path, '#captions');
+        }
         // <if expr="chromeos">
         if (settings.routes.MANAGE_ACCESSIBILITY) {
           map.set(
@@ -53,6 +56,17 @@
       },
     },
 
+    /**
+     * Whether to show the link to caption settings.
+     * @private {boolean}
+     */
+    showCaptionSettings_: {
+      type: Boolean,
+      value: function() {
+        return loadTimeData.getBoolean('enableCaptionSettings');
+      },
+    },
+
     // <if expr="chromeos">
     /**
      * Whether to show experimental accessibility features.
@@ -121,4 +135,29 @@
     window.open(
         'https://chrome.google.com/webstore/category/collection/accessibility');
   },
+
+  /** @private */
+  onTapCaptions_: function() {
+    // Open the system captions dialog for Mac.
+    // <if expr="is_macosx">
+    settings.CaptionsBrowserProxyImpl.getInstance().openSystemCaptionsDialog();
+    // </if>
+
+    // Open the system captions dialog for Windows 10+ or navigate to the
+    // caption settings page for older versions of Windows
+    // <if expr="is_win">
+    if (loadTimeData.getBoolean('isWindows10OrNewer')) {
+      settings.CaptionsBrowserProxyImpl.getInstance()
+          .openSystemCaptionsDialog();
+    } else {
+      settings.navigateTo(settings.routes.CAPTIONS);
+    }
+    // </if>
+
+    // Navigate to the caption settings page for ChromeOS and Linux as they
+    // do not have system caption settings.
+    // <if expr="chromeos or is_linux">
+    settings.navigateTo(settings.routes.CAPTIONS);
+    // </if>
+  },
 });
diff --git a/chrome/browser/resources/settings/a11y_page/captions_browser_proxy.html b/chrome/browser/resources/settings/a11y_page/captions_browser_proxy.html
new file mode 100644
index 0000000..92b4cd1
--- /dev/null
+++ b/chrome/browser/resources/settings/a11y_page/captions_browser_proxy.html
@@ -0,0 +1,2 @@
+<link rel="import" href="chrome://resources/html/cr.html">
+<script src="captions_browser_proxy.js"></script>
diff --git a/chrome/browser/resources/settings/a11y_page/captions_browser_proxy.js b/chrome/browser/resources/settings/a11y_page/captions_browser_proxy.js
new file mode 100644
index 0000000..bf86a7e
--- /dev/null
+++ b/chrome/browser/resources/settings/a11y_page/captions_browser_proxy.js
@@ -0,0 +1,35 @@
+// 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.
+
+/**
+ * @fileoverview A helper object used from the Chrome captions section to
+ * interact with the browser. Used on operating system that is not Chrome OS.
+ */
+
+cr.define('settings', function() {
+  /** @interface */
+  class CaptionsBrowserProxy {
+    /**
+     * Open the native captions system dialog.
+     */
+    openSystemCaptionsDialog() {}
+  }
+
+  /**
+   * @implements {settings.CaptionsBrowserProxy}
+   */
+  class CaptionsBrowserProxyImpl {
+    /** @override */
+    openSystemCaptionsDialog() {
+      chrome.send('openSystemCaptionsDialog');
+    }
+  }
+
+  cr.addSingletonGetter(CaptionsBrowserProxyImpl);
+
+  return {
+    CaptionsBrowserProxy: CaptionsBrowserProxy,
+    CaptionsBrowserProxyImpl: CaptionsBrowserProxyImpl,
+  };
+});
diff --git a/chrome/browser/resources/settings/a11y_page/captions_subpage.html b/chrome/browser/resources/settings/a11y_page/captions_subpage.html
new file mode 100644
index 0000000..31146eb
--- /dev/null
+++ b/chrome/browser/resources/settings/a11y_page/captions_subpage.html
@@ -0,0 +1,89 @@
+<link rel="import" href="chrome://resources/html/i18n_behavior.html">
+<link rel="import" href="chrome://resources/html/polymer.html">
+<link rel="import" href="../appearance_page/fonts_browser_proxy.html">
+<link rel="import" href="../controls/settings_dropdown_menu.html">
+<link rel="import" href="../controls/settings_slider.html">
+<link rel="import" href="../settings_shared_css.html">
+
+<dom-module id="settings-captions">
+  <template>
+    <style include="settings-shared"></style>
+    <div class="settings-box">
+      <div class="start">$i18n{captionsTextSize}</div>
+      <settings-dropdown-menu id="captionsTextSize"
+          label="$i18n{captionsTextSize}"
+          pref="{{prefs.accessibility.captions.text_size}}"
+          menu-options="[[textSizeOptions_]]">
+      </settings-dropdown-menu>
+    </div>
+    <div class="settings-box">
+      <div class="start">$i18n{captionsTextFont}</div>
+      <settings-dropdown-menu id="captionsTextFont"
+          label="$i18n{captionsTextFont}"
+          pref="{{prefs.accessibility.captions.text_font}}"
+          menu-options="[[textFontOptions_]]">
+      </settings-dropdown-menu>
+    </div>
+    <div class="settings-box">
+      <div class="start">$i18n{captionsTextColor}</div>
+      <settings-dropdown-menu id="captionsTextColor"
+          label="$i18n{captionsTextColor}"
+          pref="{{prefs.accessibility.captions.text_color}}"
+          menu-options="[[colorOptions_]]">
+      </settings-dropdown-menu>
+    </div>
+    <div class="settings-box">
+      <div class="start">$i18n{captionsTextOpacity}</div>
+      <settings-slider id="captionsTextOpacity"
+          ticks="[[textOpacityRange_]]"
+          label-min="$i18n{captionsOpacityMin}"
+          label-max="$i18n{captionsOpacityMax}"
+          pref="{{prefs.accessibility.captions.text_opacity}}">
+      </settings-slider>
+    </div>
+    <div class="settings-box">
+      <div class="start">$i18n{captionsTextShadow}</div>
+      <settings-dropdown-menu id="captionsTextShadow"
+          label="$i18n{captionsTextShadow}"
+          pref="{{prefs.accessibility.captions.text_shadow}}"
+          menu-options="[[textShadowOptions_]]">
+      </settings-dropdown-menu>
+    </div>
+    <div class="settings-box">
+      <div class="start">$i18n{captionsBackgroundColor}</div>
+      <settings-dropdown-menu id="captionsBackgroundColor"
+          label="$i18n{captionsBackgroundColor}"
+          pref="{{prefs.accessibility.captions.background_color}}"
+          menu-options="[[colorOptions_]]">
+      </settings-dropdown-menu>
+    </div>
+    <div class="settings-box">
+      <div class="start">$i18n{captionsBackgroundOpacity}</div>
+      <settings-slider id="captionsBackgroundOpacity"
+          ticks="[[textOpacityRange_]]"
+          label-min="$i18n{captionsOpacityMin}"
+          label-max="$i18n{captionsOpacityMax}"
+          pref="{{prefs.accessibility.captions.background_opacity}}">
+      </settings-slider>
+    </div>
+    <div class="list-frame">
+      <div class="list-item">
+        <span style="
+              font-size:[[prefs.accessibility.captions.text_size.value]];
+              font-family:[[prefs.accessibility.captions.text_font.value]];
+              background-color: [[computeBackgroundColor_(
+                  prefs.accessibility.captions.background_opacity.value,
+                  prefs.accessibility.captions.background_color.value)]];
+              color: [[computeTextColor_(
+                  prefs.accessibility.captions.text_opacity.value,
+                  prefs.accessibility.captions.text_color.value)]];
+              text-shadow: [[prefs.accessibility.captions.text_shadow.value]];
+              padding: [[computePadding_(
+                  prefs.accessibility.captions.text_size.value)]]">
+          $i18n{quickBrownFox}
+        </span>
+      </div>
+    </div>
+  </template>
+  <script src="captions_subpage.js"></script>
+</dom-module>
diff --git a/chrome/browser/resources/settings/a11y_page/captions_subpage.js b/chrome/browser/resources/settings/a11y_page/captions_subpage.js
new file mode 100644
index 0000000..df973af
--- /dev/null
+++ b/chrome/browser/resources/settings/a11y_page/captions_subpage.js
@@ -0,0 +1,217 @@
+// 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.
+
+/**
+ * @fileoverview 'settings-captions' is a component for showing captions
+ * settings subpage (chrome://settings/captions).
+ */
+(function() {
+'use strict';
+
+
+/** @type {!Array<number>} */
+const TEXT_OPACITY_RANGE = [
+  0,  5,  10, 15, 20, 25, 30, 35, 40, 45, 50,
+  55, 60, 65, 70, 75, 80, 85, 90, 95, 100
+];
+
+/**
+ * @param {!Array<number>} ticks
+ * @return {!Array<!cr_slider.SliderTick>}
+ */
+function ticksWithLabels(ticks) {
+  return ticks.map(x => ({label: `${x}`, value: x}));
+}
+
+Polymer({
+  is: 'settings-captions',
+
+  behaviors: [I18nBehavior, WebUIListenerBehavior],
+
+  properties: {
+    prefs: {
+      type: Object,
+      notify: true,
+    },
+
+    /**
+     * List of fonts populated by the fonts browser proxy.
+     * @private {!DropdownMenuOptionList} */
+    textFontOptions_: Object,
+
+    /**
+     * Reasonable, text opacity range.
+     * @private {!Array<!cr_slider.SliderTick>}
+     */
+    textOpacityRange_: {
+      readOnly: true,
+      type: Array,
+      value: ticksWithLabels(TEXT_OPACITY_RANGE),
+    },
+
+    /**
+     * List of options for the text size drop-down menu.
+     * @type {!DropdownMenuOptionList}
+     */
+    textSizeOptions_: {
+      readOnly: true,
+      type: Array,
+      value: function() {
+        return [
+          {value: '50%', name: loadTimeData.getString('verySmall')},
+          {value: '75%', name: loadTimeData.getString('small')},
+          {value: '', name: loadTimeData.getString('medium')}, // Default = 100%
+          {value: '150%', name: loadTimeData.getString('large')},
+          {value: '200%', name: loadTimeData.getString('veryLarge')},
+        ];
+      },
+    },
+
+    /**
+     * List of options for the color drop-down menu.
+     * @type {!DropdownMenuOptionList}
+     */
+    colorOptions_: {
+      readOnly: true,
+      type: Array,
+      value: function() {
+        return [
+          {value: '', name: loadTimeData.getString('captionsDefaultSetting')},
+          {value: '0,0,0', name: loadTimeData.getString('captionsColorBlack')},
+          {
+            value: '255,255,255',
+            name: loadTimeData.getString('captionsColorWhite')
+          },
+          {value: '255,0,0', name: loadTimeData.getString('captionsColorRed')},
+          {
+            value: '0,255,0',
+            name: loadTimeData.getString('captionsColorGreen')
+          },
+          {value: '0,0,255', name: loadTimeData.getString('captionsColorBlue')},
+          {
+            value: '255,255,0',
+            name: loadTimeData.getString('captionsColorYellow')
+          },
+          {
+            value: '0,255,255',
+            name: loadTimeData.getString('captionsColorCyan')
+          },
+          {
+            value: '255,0,255',
+            name: loadTimeData.getString('captionsColorMagenta')
+          },
+        ];
+      },
+    },
+
+    /**
+     * List of options for the text shadow drop-down menu.
+     * @type {!DropdownMenuOptionList}
+     */
+    textShadowOptions_: {
+      readOnly: true,
+      type: Array,
+      value: function() {
+        return [
+          {value: '', name: loadTimeData.getString('captionsTextShadowNone')},
+          {
+            value: '-2px -2px 4px rgba(0, 0, 0, 0.5)',
+            name: loadTimeData.getString('captionsTextShadowRaised')
+          },
+          {
+            value: '2px 2px 4px rgba(0, 0, 0, 0.5)',
+            name: loadTimeData.getString('captionsTextShadowDepressed')
+          },
+          {
+            value: '-1px 0px 0px black, ' +
+                '0px -1px 0px black, 1px 0px 0px black, 0px  1px 0px black',
+            name: loadTimeData.getString('captionsTextShadowUniform')
+          },
+          {
+            value: '0px 0px 2px rgba(0, 0, 0, 0.5), 2px 2px 2px black',
+            name: loadTimeData.getString('captionsTextShadowDropShadow')
+          },
+        ];
+      },
+    },
+  },
+
+  /** @private {?settings.FontsBrowserProxy} */
+  browserProxy_: null,
+
+  /** @override */
+  created: function() {
+    this.browserProxy_ = settings.FontsBrowserProxyImpl.getInstance();
+  },
+
+  /** @override */
+  ready: function() {
+    this.browserProxy_.observeAdvancedFontExtensionAvailable();
+
+    this.browserProxy_.fetchFontsData().then(this.setFontsData_.bind(this));
+  },
+
+  /**
+   * @param {!FontsData} response A list of fonts.
+   * @private
+   */
+  setFontsData_: function(response) {
+    const fontMenuOptions =
+        [{value: '', name: loadTimeData.getString('captionsDefaultSetting')}];
+    for (const fontData of response.fontList) {
+      fontMenuOptions.push({value: fontData[0], name: fontData[1]});
+    }
+    this.textFontOptions_ = fontMenuOptions;
+  },
+
+  /**
+   * Get the background color as a RGBA string.
+   * @return {string}
+   * @private
+   */
+  computeBackgroundColor_: function() {
+    return this.formatRGAString_(
+        'prefs.accessibility.captions.background_color.value',
+        'prefs.accessibility.captions.background_opacity.value');
+  },
+
+  /**
+   * Get the text color as a RGBA string.
+   * @return {string}
+   * @private
+   */
+  computeTextColor_: function() {
+    return this.formatRGAString_(
+        'prefs.accessibility.captions.text_color.value',
+        'prefs.accessibility.captions.text_opacity.value');
+  },
+
+  /**
+   * Formats the color as an RGBA string.
+   * @param {string} colorPreference The name of the preference containing the
+   * RGB values as a comma-separated string.
+   * @param {string} opacityPreference The name of the preference containing
+   * the opacity value as a percentage.
+   * @return {string} The formatted RGBA string.
+   * @private
+   */
+  formatRGAString_: function(colorPreference, opacityPreference) {
+    return 'rgba(' + this.get(colorPreference) + ',' +
+        parseInt(this.get(opacityPreference), 10) / 100.0 + ')';
+  },
+
+  /**
+   * @param {string} size The font size of the captions text as a percentage.
+   * @return {string} The padding around the captions text as a percentage.
+   * @private
+   */
+  computePadding_: function(size) {
+    if (size == '') {
+      return '1%';
+    }
+
+    return `${+size.slice(0, -1) / 100}%`;
+  }
+});
+})();
diff --git a/chrome/browser/resources/settings/route.js b/chrome/browser/resources/settings/route.js
index ba363d6..afa2d17 100644
--- a/chrome/browser/resources/settings/route.js
+++ b/chrome/browser/resources/settings/route.js
@@ -26,6 +26,7 @@
  *   BASIC: (undefined|!settings.Route),
  *   BLUETOOTH: (undefined|!settings.Route),
  *   BLUETOOTH_DEVICES: (undefined|!settings.Route),
+ *   CAPTIONS: (undefined|!settings.Route),
  *   CERTIFICATES: (undefined|!settings.Route),
  *   CHANGE_PICTURE: (undefined|!settings.Route),
  *   CHROME_CLEANUP: (undefined|!settings.Route),
@@ -463,6 +464,20 @@
       // </if>
 
       r.ACCESSIBILITY = r.ADVANCED.createSection('/accessibility', 'a11y');
+
+      // <if expr="chromeos or is_linux">
+      if (loadTimeData.getBoolean('enableCaptionSettings')) {
+        r.CAPTIONS = r.ACCESSIBILITY.createChild('/captions');
+      }
+      // </if>
+
+      // <if expr="is_win">
+      if (loadTimeData.getBoolean('enableCaptionSettings') &&
+          !loadTimeData.getBoolean('isWindows10OrNewer')) {
+        r.CAPTIONS = r.ACCESSIBILITY.createChild('/captions');
+      }
+      // </if>
+
       // <if expr="chromeos">
       r.MANAGE_ACCESSIBILITY =
           r.ACCESSIBILITY.createChild('/manageAccessibility');
diff --git a/chrome/browser/resources/settings/settings_resources.grd b/chrome/browser/resources/settings/settings_resources.grd
index 6e6b2c12..beae2b9 100644
--- a/chrome/browser/resources/settings/settings_resources.grd
+++ b/chrome/browser/resources/settings/settings_resources.grd
@@ -21,6 +21,18 @@
                  type="chrome_html"
                  preprocess="true"
                  allowexternalscript="true" />
+      <structure name="IDR_SETTINGS_CAPTIONS_SUBPAGE_HTML"
+                 file="a11y_page/captions_subpage.html"
+                 type="chrome_html" />
+      <structure name="IDR_SETTINGS_CAPTIONS_SUBPAGE_JS"
+                 file="a11y_page/captions_subpage.js"
+                 type="chrome_html" />
+      <structure name="IDR_SETTINGS_CAPTIONS_BROWSER_PROXY_HTML"
+                 file="a11y_page/captions_browser_proxy.html"
+                 type="chrome_html" />
+      <structure name="IDR_SETTINGS_CAPTIONS_BROWSER_PROXY_JS"
+                 file="a11y_page/captions_browser_proxy.js"
+                 type="chrome_html" />
       <if expr="chromeos">
         <structure name="IDR_SETTINGS_MANAGE_A11Y_PAGE_JS"
                    file="a11y_page/manage_a11y_page.js"
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index c34eab4b..9ba3f91e 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -335,6 +335,13 @@
     "webui/webui_util.h",
   ]
 
+  if (is_win || is_mac) {
+    sources += [
+      "webui/settings/captions_handler.cc",
+      "webui/settings/captions_handler.h",
+    ]
+  }
+
   if (enable_vr) {
     if (is_win) {
       sources += [
diff --git a/chrome/browser/ui/prefs/pref_watcher.cc b/chrome/browser/ui/prefs/pref_watcher.cc
index adec473..44cffec 100644
--- a/chrome/browser/ui/prefs/pref_watcher.cc
+++ b/chrome/browser/ui/prefs/pref_watcher.cc
@@ -30,6 +30,13 @@
     prefs::kWebKitDefaultFixedFontSize,
     prefs::kWebKitDefaultFontSize,
     prefs::kWebKitDomPasteEnabled,
+    prefs::kAccessibilityCaptionsTextSize,
+    prefs::kAccessibilityCaptionsTextFont,
+    prefs::kAccessibilityCaptionsTextColor,
+    prefs::kAccessibilityCaptionsTextOpacity,
+    prefs::kAccessibilityCaptionsBackgroundColor,
+    prefs::kAccessibilityCaptionsTextShadow,
+    prefs::kAccessibilityCaptionsBackgroundOpacity,
 #if defined(OS_ANDROID)
     prefs::kWebKitFontScaleFactor,
     prefs::kWebKitForceDarkModeEnabled,
diff --git a/chrome/browser/ui/webui/settings/captions_handler.cc b/chrome/browser/ui/webui/settings/captions_handler.cc
new file mode 100644
index 0000000..f91f2fdd
--- /dev/null
+++ b/chrome/browser/ui/webui/settings/captions_handler.cc
@@ -0,0 +1,34 @@
+// 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.
+
+#include "chrome/browser/ui/webui/settings/captions_handler.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "chrome/browser/accessibility/caption_settings_dialog.h"
+#include "content/public/browser/web_ui.h"
+
+namespace settings {
+
+CaptionsHandler::CaptionsHandler() {}
+
+CaptionsHandler::~CaptionsHandler() {}
+
+void CaptionsHandler::RegisterMessages() {
+  web_ui()->RegisterMessageCallback(
+      "openSystemCaptionsDialog",
+      base::BindRepeating(&CaptionsHandler::HandleOpenSystemCaptionsDialog,
+                          base::Unretained(this)));
+}
+
+void CaptionsHandler::OnJavascriptAllowed() {}
+
+void CaptionsHandler::OnJavascriptDisallowed() {}
+
+void CaptionsHandler::HandleOpenSystemCaptionsDialog(
+    const base::ListValue* args) {
+  captions::CaptionSettingsDialog::ShowCaptionSettingsDialog();
+}
+
+}  // namespace settings
diff --git a/chrome/browser/ui/webui/settings/captions_handler.h b/chrome/browser/ui/webui/settings/captions_handler.h
new file mode 100644
index 0000000..28ea30d
--- /dev/null
+++ b/chrome/browser/ui/webui/settings/captions_handler.h
@@ -0,0 +1,34 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_CAPTIONS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_SETTINGS_CAPTIONS_HANDLER_H_
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
+
+namespace settings {
+
+// UI handler for Chrome caption settings subpage on operating systems other
+// than Chrome OS and Linux.
+class CaptionsHandler : public SettingsPageUIHandler {
+ public:
+  CaptionsHandler();
+  ~CaptionsHandler() override;
+
+  // SettingsPageUIHandler overrides:
+  void RegisterMessages() override;
+  void OnJavascriptAllowed() override;
+  void OnJavascriptDisallowed() override;
+
+ private:
+  void HandleOpenSystemCaptionsDialog(const base::ListValue* args);
+
+  DISALLOW_COPY_AND_ASSIGN(CaptionsHandler);
+};
+
+}  // namespace settings
+
+#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_CAPTIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index eb52546..10ac391 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -12,6 +12,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/win/windows_version.h"
 #include "build/build_config.h"
 #include "build/buildflag.h"
 #include "chrome/browser/autofill/personal_data_manager_factory.h"
@@ -191,6 +192,32 @@
     {"moreFeaturesLink", IDS_SETTINGS_MORE_FEATURES_LINK},
     {"moreFeaturesLinkDescription",
      IDS_SETTINGS_MORE_FEATURES_LINK_DESCRIPTION},
+    {"captionsTitle", IDS_SETTINGS_CAPTIONS},
+    {"captionsTextSize", IDS_SETTINGS_CAPTIONS_TEXT_SIZE},
+    {"captionsTextFont", IDS_SETTINGS_CAPTIONS_TEXT_FONT},
+    {"captionsTextColor", IDS_SETTINGS_CAPTIONS_TEXT_COLOR},
+    {"captionsTextOpacity", IDS_SETTINGS_CAPTIONS_TEXT_OPACITY},
+    {"captionsBackgroundOpacity", IDS_SETTINGS_CAPTIONS_BACKGROUND_OPACITY},
+    {"captionsOpacityMin", IDS_SETTINGS_CAPTIONS_OPACITY_MIN},
+    {"captionsOpacityMax", IDS_SETTINGS_CAPTIONS_OPACITY_MAX},
+    {"captionsTextShadow", IDS_SETTINGS_CAPTIONS_TEXT_SHADOW},
+    {"captionsTextShadowNone", IDS_SETTINGS_CAPTIONS_TEXT_SHADOW_NONE},
+    {"captionsTextShadowRaised", IDS_SETTINGS_CAPTIONS_TEXT_SHADOW_RAISED},
+    {"captionsTextShadowDepressed",
+     IDS_SETTINGS_CAPTIONS_TEXT_SHADOW_DEPRESSED},
+    {"captionsTextShadowUniform", IDS_SETTINGS_CAPTIONS_TEXT_SHADOW_UNIFORM},
+    {"captionsTextShadowDropShadow",
+     IDS_SETTINGS_CAPTIONS_TEXT_SHADOW_DROP_SHADOW},
+    {"captionsBackgroundColor", IDS_SETTINGS_CAPTIONS_BACKGROUND_COLOR},
+    {"captionsColorBlack", IDS_SETTINGS_CAPTIONS_COLOR_BLACK},
+    {"captionsColorWhite", IDS_SETTINGS_CAPTIONS_COLOR_WHITE},
+    {"captionsColorRed", IDS_SETTINGS_CAPTIONS_COLOR_RED},
+    {"captionsColorGreen", IDS_SETTINGS_CAPTIONS_COLOR_GREEN},
+    {"captionsColorBlue", IDS_SETTINGS_CAPTIONS_COLOR_BLUE},
+    {"captionsColorYellow", IDS_SETTINGS_CAPTIONS_COLOR_YELLOW},
+    {"captionsColorCyan", IDS_SETTINGS_CAPTIONS_COLOR_CYAN},
+    {"captionsColorMagenta", IDS_SETTINGS_CAPTIONS_COLOR_MAGENTA},
+    {"captionsDefaultSetting", IDS_SETTINGS_CAPTIONS_DEFAULT_SETTING},
 #if defined(OS_CHROMEOS)
     {"optionsInMenuLabel", IDS_SETTINGS_OPTIONS_IN_MENU_LABEL},
     {"largeMouseCursorLabel", IDS_SETTINGS_LARGE_MOUSE_CURSOR_LABEL},
@@ -344,6 +371,15 @@
       "showExperimentalA11yLabels",
       base::FeatureList::IsEnabled(features::kExperimentalAccessibilityLabels));
 
+  html_source->AddBoolean(
+      "enableCaptionSettings",
+      base::FeatureList::IsEnabled(features::kCaptionSettings));
+
+#if defined(OS_WIN)
+  html_source->AddBoolean("isWindows10OrNewer",
+                          base::win::GetVersion() >= base::win::Version::WIN10);
+#endif
+
 #if defined(OS_CHROMEOS)
   html_source->AddString("accountManagerLearnMoreUrl",
                          chrome::kAccountManagerLearnMoreURL);
diff --git a/chrome/browser/ui/webui/settings/settings_ui.cc b/chrome/browser/ui/webui/settings/settings_ui.cc
index fe76537..c5a2b951 100644
--- a/chrome/browser/ui/webui/settings/settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui.cc
@@ -26,6 +26,7 @@
 #include "chrome/browser/ui/webui/settings/accessibility_main_handler.h"
 #include "chrome/browser/ui/webui/settings/appearance_handler.h"
 #include "chrome/browser/ui/webui/settings/browser_lifetime_handler.h"
+#include "chrome/browser/ui/webui/settings/captions_handler.h"
 #include "chrome/browser/ui/webui/settings/downloads_handler.h"
 #include "chrome/browser/ui/webui/settings/extension_control_handler.h"
 #include "chrome/browser/ui/webui/settings/font_handler.h"
@@ -226,6 +227,10 @@
   AddSettingsPageUIHandler(std::make_unique<StartupPagesHandler>(web_ui));
   AddSettingsPageUIHandler(std::make_unique<SecurityKeysHandler>());
 
+#if defined(OS_WIN) || defined(OS_MACOSX)
+  AddSettingsPageUIHandler(std::make_unique<CaptionsHandler>());
+#endif
+
 #if defined(OS_CHROMEOS)
   // TODO(950007): Remove this when SplitSettings is the default and there are
   // no Chrome OS settings in the browser settings page.