Add accessibilityPrivate API method to show confirmation dialog.

Adds a method to the Accessibility Private JS extension API to
show a confirmation dialog with a given title and description,
and to call a callback when the dialog is accepted or canceled/closed.

Moves AccessibilityPrivateAPITest into chrome/browser/ash/ so that
it can access AccessibilityController.

Bug: 1103799
Change-Id: I0618b2ef7b5e56b05e769d29b4bf9841ba4179fd
AX-Relnotes: N/A
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2765388
Reviewed-by: Xiyuan Xia <xiyuan@chromium.org>
Reviewed-by: Ahmed Fakhry <afakhry@chromium.org>
Reviewed-by: David Tseng <dtseng@chromium.org>
Commit-Queue: Katie Dektar <katie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#864092}
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index f14907a1..3e140de0 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -110,8 +110,6 @@
   sources = [
     "accelerators/accelerator_commands.cc",
     "accelerators/accelerator_commands.h",
-    "accelerators/accelerator_confirmation_dialog.cc",
-    "accelerators/accelerator_confirmation_dialog.h",
     "accelerators/accelerator_controller_impl.cc",
     "accelerators/accelerator_controller_impl.h",
     "accelerators/accelerator_history_impl.cc",
@@ -140,6 +138,8 @@
     "accelerometer/accelerometer_reader.h",
     "accelerometer/accelerometer_types.cc",
     "accelerometer/accelerometer_types.h",
+    "accessibility/accessibility_confirmation_dialog.cc",
+    "accessibility/accessibility_confirmation_dialog.h",
     "accessibility/accessibility_controller_impl.cc",
     "accessibility/accessibility_cursor_ring_layer.cc",
     "accessibility/accessibility_cursor_ring_layer.h",
diff --git a/ash/accelerators/accelerator_confirmation_dialog.h b/ash/accelerators/accelerator_confirmation_dialog.h
deleted file mode 100644
index 38229e3..0000000
--- a/ash/accelerators/accelerator_confirmation_dialog.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2018 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 ASH_ACCELERATORS_ACCELERATOR_CONFIRMATION_DIALOG_H_
-#define ASH_ACCELERATORS_ACCELERATOR_CONFIRMATION_DIALOG_H_
-
-#include <string>
-
-#include "base/callback_forward.h"
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "ui/views/window/dialog_delegate.h"
-
-namespace ash {
-
-// Defines a dialog for accelerators that require confirmation from users prior
-// to perform.
-class AcceleratorConfirmationDialog : public views::DialogDelegateView {
- public:
-  AcceleratorConfirmationDialog(int window_title_text_id,
-                                int dialog_text_id,
-                                base::OnceClosure on_accept_callback,
-                                base::OnceClosure on_cancel_callback);
-  ~AcceleratorConfirmationDialog() override;
-
-  base::WeakPtr<AcceleratorConfirmationDialog> GetWeakPtr();
-
- private:
-  base::WeakPtrFactory<AcceleratorConfirmationDialog> weak_ptr_factory_{this};
-
-  DISALLOW_COPY_AND_ASSIGN(AcceleratorConfirmationDialog);
-};
-
-}  // namespace ash
-
-#endif  // ASH_ACCELERATORS_ACCELERATOR_CONFIRMATION_DIALOG_H_
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc
index 9241cf2..c1999dd 100644
--- a/ash/accelerators/accelerator_controller_impl.cc
+++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -10,8 +10,8 @@
 #include <utility>
 
 #include "ash/accelerators/accelerator_commands.h"
-#include "ash/accelerators/accelerator_confirmation_dialog.h"
 #include "ash/accelerators/debug_commands.h"
+#include "ash/accessibility/accessibility_confirmation_dialog.h"
 #include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/ambient/ambient_controller.h"
 #include "ash/app_list/app_list_controller_impl.h"
@@ -1666,7 +1666,7 @@
   return it->second;
 }
 
-AcceleratorConfirmationDialog*
+AccessibilityConfirmationDialog*
 AcceleratorControllerImpl::TestApi::GetConfirmationDialog() {
   return controller_->confirmation_dialog_.get();
 }
@@ -2578,9 +2578,10 @@
   if (confirmation_dialog_)
     return;
 
-  auto* dialog = new AcceleratorConfirmationDialog(
-      window_title_text_id, dialog_text_id, std::move(on_accept_callback),
-      std::move(on_cancel_callback));
+  auto* dialog = new AccessibilityConfirmationDialog(
+      l10n_util::GetStringUTF16(window_title_text_id),
+      l10n_util::GetStringUTF16(dialog_text_id), std::move(on_accept_callback),
+      std::move(on_cancel_callback), /* on close */ base::DoNothing());
   confirmation_dialog_ = dialog->GetWeakPtr();
 }
 
diff --git a/ash/accelerators/accelerator_controller_impl.h b/ash/accelerators/accelerator_controller_impl.h
index 2f0fddd..841a11da 100644
--- a/ash/accelerators/accelerator_controller_impl.h
+++ b/ash/accelerators/accelerator_controller_impl.h
@@ -12,10 +12,10 @@
 #include <set>
 #include <vector>
 
-#include "ash/accelerators/accelerator_confirmation_dialog.h"
 #include "ash/accelerators/accelerator_history_impl.h"
 #include "ash/accelerators/accelerator_table.h"
 #include "ash/accelerators/exit_warning_handler.h"
+#include "ash/accessibility/accessibility_confirmation_dialog.h"
 #include "ash/ash_export.h"
 #include "ash/public/cpp/accelerators.h"
 #include "base/compiler_specific.h"
@@ -112,7 +112,7 @@
         AcceleratorAction action);
 
     // Accessor to accelerator confirmation dialog.
-    AcceleratorConfirmationDialog* GetConfirmationDialog();
+    AccessibilityConfirmationDialog* GetConfirmationDialog();
 
     AcceleratorControllerImpl::SideVolumeButtonLocation
     side_volume_button_location() {
@@ -334,8 +334,8 @@
   // Actions that can be performed without closing the menu (if one is present).
   std::set<int> actions_keeping_menu_open_;
 
-  // Holds a weak pointer to the accelerator confirmation dialog.
-  base::WeakPtr<AcceleratorConfirmationDialog> confirmation_dialog_;
+  // Holds a weak pointer to the accessibility confirmation dialog.
+  base::WeakPtr<AccessibilityConfirmationDialog> confirmation_dialog_;
 
   // Path of the file that contains the side volume button location info. It
   // should always be kSideVolumeButtonLocationFilePath. But it is allowed to be
diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc
index 1518c34..728a94a 100644
--- a/ash/accelerators/accelerator_controller_unittest.cc
+++ b/ash/accelerators/accelerator_controller_unittest.cc
@@ -6,10 +6,10 @@
 
 #include <utility>
 
-#include "ash/accelerators/accelerator_confirmation_dialog.h"
 #include "ash/accelerators/accelerator_history_impl.h"
 #include "ash/accelerators/accelerator_table.h"
 #include "ash/accelerators/pre_target_accelerator_handler.h"
+#include "ash/accessibility/accessibility_confirmation_dialog.h"
 #include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/accessibility/test_accessibility_controller_client.h"
 #include "ash/app_list/app_list_metrics.h"
diff --git a/ash/accelerators/accelerator_confirmation_dialog.cc b/ash/accessibility/accessibility_confirmation_dialog.cc
similarity index 75%
rename from ash/accelerators/accelerator_confirmation_dialog.cc
rename to ash/accessibility/accessibility_confirmation_dialog.cc
index 9e6aa14..f58cd9f 100644
--- a/ash/accelerators/accelerator_confirmation_dialog.cc
+++ b/ash/accessibility/accessibility_confirmation_dialog.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ash/accelerators/accelerator_confirmation_dialog.h"
+#include "ash/accessibility/accessibility_confirmation_dialog.h"
 
 #include <memory>
 #include <utility>
@@ -22,24 +22,25 @@
 
 namespace ash {
 
-AcceleratorConfirmationDialog::AcceleratorConfirmationDialog(
-    int window_title_text_id,
-    int dialog_text_id,
+AccessibilityConfirmationDialog::AccessibilityConfirmationDialog(
+    const std::u16string& window_title_text,
+    const std::u16string& dialog_text,
     base::OnceClosure on_accept_callback,
-    base::OnceClosure on_cancel_callback) {
+    base::OnceClosure on_cancel_callback,
+    base::OnceClosure on_close_callback) {
   SetModalType(ui::MODAL_TYPE_SYSTEM);
-  SetTitle(l10n_util::GetStringUTF16(window_title_text_id));
+  SetTitle(window_title_text);
   SetButtonLabel(ui::DIALOG_BUTTON_OK,
                  l10n_util::GetStringUTF16(IDS_ASH_CONTINUE_BUTTON));
   SetAcceptCallback(std::move(on_accept_callback));
   SetCancelCallback(std::move(on_cancel_callback));
+  SetCloseCallback(std::move(on_close_callback));
 
   SetLayoutManager(std::make_unique<views::FillLayout>());
   SetBorder(views::CreateEmptyBorder(
       views::LayoutProvider::Get()->GetDialogInsetsForContentType(
           views::TEXT, views::TEXT)));
-  AddChildView(std::make_unique<views::Label>(
-      l10n_util::GetStringUTF16(dialog_text_id)));
+  AddChildView(std::make_unique<views::Label>(dialog_text));
 
   // Parent the dialog widget to the LockSystemModalContainer to ensure that it
   // will get displayed on respective lock/signin or OOBE screen.
@@ -58,10 +59,10 @@
   widget->Show();
 }
 
-AcceleratorConfirmationDialog::~AcceleratorConfirmationDialog() = default;
+AccessibilityConfirmationDialog::~AccessibilityConfirmationDialog() = default;
 
-base::WeakPtr<AcceleratorConfirmationDialog>
-AcceleratorConfirmationDialog::GetWeakPtr() {
+base::WeakPtr<AccessibilityConfirmationDialog>
+AccessibilityConfirmationDialog::GetWeakPtr() {
   return weak_ptr_factory_.GetWeakPtr();
 }
 
diff --git a/ash/accessibility/accessibility_confirmation_dialog.h b/ash/accessibility/accessibility_confirmation_dialog.h
new file mode 100644
index 0000000..6156c54
--- /dev/null
+++ b/ash/accessibility/accessibility_confirmation_dialog.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2018 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 ASH_ACCESSIBILITY_ACCESSIBILITY_CONFIRMATION_DIALOG_H_
+#define ASH_ACCESSIBILITY_ACCESSIBILITY_CONFIRMATION_DIALOG_H_
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "ui/views/window/dialog_delegate.h"
+
+namespace ash {
+
+// Defines a dialog for accessibility that require confirmation from users prior
+// to performing some action.
+class AccessibilityConfirmationDialog : public views::DialogDelegateView {
+ public:
+  AccessibilityConfirmationDialog(const std::u16string& window_title_text,
+                                  const std::u16string& dialog_text,
+                                  base::OnceClosure on_accept_callback,
+                                  base::OnceClosure on_cancel_callback,
+                                  base::OnceClosure on_close_callback);
+  ~AccessibilityConfirmationDialog() override;
+  AccessibilityConfirmationDialog(const AccessibilityConfirmationDialog&) =
+      delete;
+  AccessibilityConfirmationDialog& operator=(
+      const AccessibilityConfirmationDialog&) = delete;
+
+  base::WeakPtr<AccessibilityConfirmationDialog> GetWeakPtr();
+
+ private:
+  base::WeakPtrFactory<AccessibilityConfirmationDialog> weak_ptr_factory_{this};
+};
+
+}  // namespace ash
+
+#endif  // ASH_ACCESSIBILITY_ACCESSIBILITY_CONFIRMATION_DIALOG_H_
diff --git a/ash/accessibility/accessibility_controller_impl.cc b/ash/accessibility/accessibility_controller_impl.cc
index 14560ea6..0328426f 100644
--- a/ash/accessibility/accessibility_controller_impl.cc
+++ b/ash/accessibility/accessibility_controller_impl.cc
@@ -1929,6 +1929,28 @@
   enable_chromevox_volume_slide_gesture_ = true;
 }
 
+void AccessibilityControllerImpl::ShowConfirmationDialog(
+    const std::u16string& title,
+    const std::u16string& description,
+    base::OnceClosure on_accept_callback,
+    base::OnceClosure on_cancel_callback,
+    base::OnceClosure on_close_callback) {
+  if (confirmation_dialog_) {
+    // If a dialog is already being shown we do not show a new one.
+    // Instead, run the on_close_callback on the new dialog to indicate
+    // it was closed without the user taking any action.
+    // This is consistent with AcceleratorController.
+    std::move(on_close_callback).Run();
+    return;
+  }
+  auto* dialog = new AccessibilityConfirmationDialog(
+      title, description, std::move(on_accept_callback),
+      std::move(on_cancel_callback), std::move(on_close_callback));
+  // Save the dialog so it doesn't go out of scope before it is
+  // used and closed.
+  confirmation_dialog_ = dialog->GetWeakPtr();
+}
+
 void AccessibilityControllerImpl::UpdateFeatureFromPref(FeatureType feature) {
   bool enabled = features_[feature]->enabled();
 
diff --git a/ash/accessibility/accessibility_controller_impl.h b/ash/accessibility/accessibility_controller_impl.h
index 4d2ae0e..8157f479 100644
--- a/ash/accessibility/accessibility_controller_impl.h
+++ b/ash/accessibility/accessibility_controller_impl.h
@@ -40,6 +40,7 @@
 
 namespace ash {
 
+class AccessibilityConfirmationDialog;
 class AccessibilityEventRewriter;
 class AccessibilityHighlightController;
 class AccessibilityObserver;
@@ -390,6 +391,11 @@
   void DisablePolicyRecommendationRestorerForTesting() override;
   void SuspendSwitchAccessKeyHandling(bool suspend) override;
   void EnableChromeVoxVolumeSlideGesture() override;
+  void ShowConfirmationDialog(const std::u16string& title,
+                              const std::u16string& description,
+                              base::OnceClosure on_accept_callback,
+                              base::OnceClosure on_cancel_callback,
+                              base::OnceClosure on_close_callback) override;
 
   // SessionObserver:
   void OnSigninScreenPrefServiceInitialized(PrefService* prefs) override;
@@ -405,6 +411,9 @@
   GetSelectToSpeakMenuBubbleControllerForTest() {
     return select_to_speak_bubble_controller_.get();
   }
+  AccessibilityConfirmationDialog* GetConfirmationDialogForTest() {
+    return confirmation_dialog_.get();
+  }
 
   bool enable_chromevox_volume_slide_gesture() {
     return enable_chromevox_volume_slide_gesture_;
@@ -514,6 +523,9 @@
   // any prefs during destruction.
   std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_;
 
+  // The current AccessibilityConfirmationDialog, if one exists.
+  base::WeakPtr<AccessibilityConfirmationDialog> confirmation_dialog_;
+
   base::WeakPtrFactory<AccessibilityControllerImpl> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(AccessibilityControllerImpl);
diff --git a/ash/public/cpp/accessibility_controller.h b/ash/public/cpp/accessibility_controller.h
index 8982b82..2a9e9bb 100644
--- a/ash/public/cpp/accessibility_controller.h
+++ b/ash/public/cpp/accessibility_controller.h
@@ -11,6 +11,7 @@
 #include "ash/public/cpp/accelerators.h"
 #include "ash/public/cpp/accessibility_controller_enums.h"
 #include "ash/public/cpp/ash_public_export.h"
+#include "base/callback.h"
 #include "base/macros.h"
 
 namespace gfx {
@@ -147,6 +148,15 @@
   // Enables ChromeVox's volume slide gesture.
   virtual void EnableChromeVoxVolumeSlideGesture() {}
 
+  // Shows a confirmation dialog with the given text and description,
+  // and calls the relevant callback when the dialog is confirmed, canceled
+  // or closed.
+  virtual void ShowConfirmationDialog(const std::u16string& title,
+                                      const std::u16string& description,
+                                      base::OnceClosure on_accept_callback,
+                                      base::OnceClosure on_cancel_callback,
+                                      base::OnceClosure on_close_callback) {}
+
  protected:
   AccessibilityController();
   virtual ~AccessibilityController();
diff --git a/chrome/browser/accessibility/accessibility_extension_api.cc b/chrome/browser/accessibility/accessibility_extension_api.cc
index 914cbf21..4f520d9 100644
--- a/chrome/browser/accessibility/accessibility_extension_api.cc
+++ b/chrome/browser/accessibility/accessibility_extension_api.cc
@@ -12,6 +12,7 @@
 #include "base/bind.h"
 #include "base/json/json_writer.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
@@ -657,4 +658,33 @@
   return RespondNow(NoArguments());
 }
 
-#endif  // defined (OS_CHROMEOS)
+ExtensionFunction::ResponseAction
+AccessibilityPrivateShowConfirmationDialogFunction::Run() {
+  std::unique_ptr<accessibility_private::ShowConfirmationDialog::Params>
+      params =
+          accessibility_private::ShowConfirmationDialog::Params::Create(*args_);
+  EXTENSION_FUNCTION_VALIDATE(params);
+
+  std::u16string title = base::UTF8ToUTF16(params->title);
+  std::u16string description = base::UTF8ToUTF16(params->description);
+  ash::AccessibilityController::Get()->ShowConfirmationDialog(
+      title, description,
+      base::BindOnce(
+          &AccessibilityPrivateShowConfirmationDialogFunction::OnDialogResult,
+          base::RetainedRef(this), /* confirmed */ true),
+      base::BindOnce(
+          &AccessibilityPrivateShowConfirmationDialogFunction::OnDialogResult,
+          base::RetainedRef(this), /* not confirmed */ false),
+      base::BindOnce(
+          &AccessibilityPrivateShowConfirmationDialogFunction::OnDialogResult,
+          base::RetainedRef(this), /* not confirmed */ false));
+
+  return RespondLater();
+}
+
+void AccessibilityPrivateShowConfirmationDialogFunction::OnDialogResult(
+    bool confirmed) {
+  Respond(OneArgument(base::Value(confirmed)));
+}
+
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/accessibility/accessibility_extension_api.h b/chrome/browser/accessibility/accessibility_extension_api.h
index 448e3a9..1f56af9 100644
--- a/chrome/browser/accessibility/accessibility_extension_api.h
+++ b/chrome/browser/accessibility/accessibility_extension_api.h
@@ -215,6 +215,17 @@
   DECLARE_EXTENSION_FUNCTION("accessibilityPrivate.updateSelectToSpeakPanel",
                              ACCESSIBILITY_PRIVATE_UPDATESELECTTOSPEAKPANEL)
 };
+
+// API function that shows a confirmation dialog, with callbacks for
+// confirm/cancel.
+class AccessibilityPrivateShowConfirmationDialogFunction
+    : public ExtensionFunction {
+  ~AccessibilityPrivateShowConfirmationDialogFunction() override = default;
+  ResponseAction Run() override;
+  void OnDialogResult(bool confirmed);
+  DECLARE_EXTENSION_FUNCTION("accessibilityPrivate.showConfirmationDialog",
+                             ACCESSIBILITY_PRIVATE_SHOWCONFIRMATIONDIALOG)
+};
 #endif  // defined (OS_CHROMEOS)
 
 #endif  // CHROME_BROWSER_ACCESSIBILITY_ACCESSIBILITY_EXTENSION_API_H_
diff --git a/chrome/browser/accessibility/accessibility_extension_api_browsertest.cc b/chrome/browser/ash/accessibility/accessibility_extension_api_browsertest.cc
similarity index 63%
rename from chrome/browser/accessibility/accessibility_extension_api_browsertest.cc
rename to chrome/browser/ash/accessibility/accessibility_extension_api_browsertest.cc
index e2b6aad..3534f59 100644
--- a/chrome/browser/accessibility/accessibility_extension_api_browsertest.cc
+++ b/chrome/browser/ash/accessibility/accessibility_extension_api_browsertest.cc
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "ash/accessibility/accessibility_confirmation_dialog.h"
+#include "ash/accessibility/accessibility_controller_impl.h"
+#include "ash/shell.h"
 #include "chrome/browser/ash/accessibility/accessibility_manager.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/browser/ui/browser_list.h"
@@ -12,6 +15,7 @@
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/common/webui_url_constants.h"
 #include "content/public/test/browser_test.h"
+#include "extensions/test/result_catcher.h"
 #include "ui/accessibility/accessibility_features.h"
 #include "ui/base/ui_base_features.h"
 
@@ -104,6 +108,69 @@
       << message_;
 }
 
+IN_PROC_BROWSER_TEST_F(AccessibilityPrivateApiTest, AcceptConfirmationDialog) {
+  ASSERT_TRUE(RunExtensionSubtest("accessibility_private/",
+                                  "accept_confirmation_dialog.html"))
+      << message_;
+
+  // The test has requested to open the confirmation dialog. Check that
+  // it was created, then confirm it.
+  ash::AccessibilityConfirmationDialog* dialog_ =
+      ash::Shell::Get()
+          ->accessibility_controller()
+          ->GetConfirmationDialogForTest();
+  ASSERT_NE(dialog_, nullptr);
+
+  EXPECT_EQ(dialog_->GetWindowTitle(), u"Confirm me! 🐶");
+
+  // Accept the dialog and wait for the JS test to get the confirmation.
+  ResultCatcher catcher;
+  dialog_->Accept();
+  EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityPrivateApiTest, CancelConfirmationDialog) {
+  ASSERT_TRUE(RunExtensionSubtest("accessibility_private/",
+                                  "cancel_confirmation_dialog.html"))
+      << message_;
+
+  // The test has requested to open the confirmation dialog. Check that
+  // it was created, then cancel it.
+  ash::AccessibilityConfirmationDialog* dialog_ =
+      ash::Shell::Get()
+          ->accessibility_controller()
+          ->GetConfirmationDialogForTest();
+  ASSERT_NE(dialog_, nullptr);
+
+  EXPECT_EQ(dialog_->GetWindowTitle(), u"Cancel me!");
+
+  // Cancel the dialog and wait for the JS test to get the callback.
+  ResultCatcher catcher;
+  dialog_->Cancel();
+  EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityPrivateApiTest, CloseConfirmationDialog) {
+  ASSERT_TRUE(RunExtensionSubtest("accessibility_private/",
+                                  "cancel_confirmation_dialog.html"))
+      << message_;
+
+  // The test has requested to open the confirmation dialog. Check that
+  // it was created, then close it.
+  ash::AccessibilityConfirmationDialog* dialog_ =
+      ash::Shell::Get()
+          ->accessibility_controller()
+          ->GetConfirmationDialogForTest();
+  ASSERT_TRUE(dialog_ != nullptr);
+
+  EXPECT_EQ(dialog_->GetWindowTitle(), u"Cancel me!");
+
+  // Close the dialog and wait for the JS test to get the callback.
+  ResultCatcher catcher;
+  dialog_->Close();
+  EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
 class AccessibilityPrivateApiFeatureEnabledTest : public ExtensionApiTest {
  public:
   void SetUp() override {
diff --git a/chrome/common/extensions/api/accessibility_private.json b/chrome/common/extensions/api/accessibility_private.json
index 20b1199..4da0fe2 100644
--- a/chrome/common/extensions/api/accessibility_private.json
+++ b/chrome/common/extensions/api/accessibility_private.json
@@ -596,6 +596,36 @@
           }
         ],
         "platforms": ["chromeos"]
+      },
+      {
+        "name": "showConfirmationDialog",
+        "type": "function",
+        "description": "Shows a confirmation dialog.",
+        "parameters": [
+          {
+            "name": "title",
+            "type": "string",
+            "description": "The title of the confirmation dialog."
+          },
+          {
+            "name": "description",
+            "type": "string",
+            "description": "The description to show within the confirmation dialog."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the dialog is confirmed or cancelled.",
+            "parameters": [
+              {
+                "type": "boolean",
+                "name": "confirmed",
+                "description": "True if the dialog was confirmed, false if it was canceled or closed."
+              }
+            ]
+          }
+        ],
+        "platforms": ["chromeos"]
       }
     ],
     "events": [
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 1df443c..b690e14 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2191,7 +2191,6 @@
 
       if (is_chromeos_ash) {
         sources += [
-          "../browser/accessibility/accessibility_extension_api_browsertest.cc",
           "../browser/apps/platform_apps/api/arc_apps_private/arc_apps_private_apitest.cc",
           "../browser/extensions/api/bluetooth_low_energy/bluetooth_low_energy_apitest_chromeos.cc",
           "../browser/extensions/api/crash_report_private/crash_report_private_apitest.cc",
@@ -2440,6 +2439,7 @@
         "../browser/apps/platform_apps/app_window_interactive_uitest_base.cc",
         "../browser/apps/platform_apps/app_window_interactive_uitest_base.h",
         "../browser/ash/accessibility/accessibility_common_browsertest.cc",
+        "../browser/ash/accessibility/accessibility_extension_api_browsertest.cc",
         "../browser/ash/accessibility/accessibility_manager_browsertest.cc",
         "../browser/ash/accessibility/accessibility_test_utils.cc",
         "../browser/ash/accessibility/accessibility_test_utils.h",
diff --git a/chrome/test/data/extensions/api_test/accessibility_private/accept_confirmation_dialog.html b/chrome/test/data/extensions/api_test/accessibility_private/accept_confirmation_dialog.html
new file mode 100644
index 0000000..496c8eb
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/accessibility_private/accept_confirmation_dialog.html
@@ -0,0 +1,7 @@
+<!--
+ * Copyright 2021 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.
+-->
+<meta charset="utf-16">
+<script src="accept_confirmation_dialog.js"></script>
diff --git a/chrome/test/data/extensions/api_test/accessibility_private/accept_confirmation_dialog.js b/chrome/test/data/extensions/api_test/accessibility_private/accept_confirmation_dialog.js
new file mode 100644
index 0000000..6527fa7
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/accessibility_private/accept_confirmation_dialog.js
@@ -0,0 +1,14 @@
+// Copyright 2021 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.
+
+chrome.accessibilityPrivate.showConfirmationDialog(
+    'Confirm me! 🐶', 'This dialog should be confirmed.', (confirmed) => {
+      if (confirmed) {
+        chrome.test.succeed();
+      } else {
+        chrome.test.fail();
+      }
+    });
+
+chrome.test.notifyPass();
diff --git a/chrome/test/data/extensions/api_test/accessibility_private/cancel_confirmation_dialog.html b/chrome/test/data/extensions/api_test/accessibility_private/cancel_confirmation_dialog.html
new file mode 100644
index 0000000..41e33dcd
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/accessibility_private/cancel_confirmation_dialog.html
@@ -0,0 +1,6 @@
+<!--
+ * Copyright 2021 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.
+-->
+<script src="cancel_confirmation_dialog.js"></script>
diff --git a/chrome/test/data/extensions/api_test/accessibility_private/cancel_confirmation_dialog.js b/chrome/test/data/extensions/api_test/accessibility_private/cancel_confirmation_dialog.js
new file mode 100644
index 0000000..f63bfdc0
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/accessibility_private/cancel_confirmation_dialog.js
@@ -0,0 +1,14 @@
+// Copyright 2021 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.
+
+chrome.accessibilityPrivate.showConfirmationDialog(
+    'Cancel me!', 'This dialog should be canceled', (confirmed) => {
+      if (confirmed) {
+        chrome.test.fail();
+      } else {
+        chrome.test.succeed();
+      }
+    });
+
+chrome.test.notifyPass();
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index c4af8d9..4bac6ba 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1606,6 +1606,7 @@
   FILEMANAGERPRIVATEINTERNAL_GETVOLUMEROOT = 1543,
   LANGUAGESETTINGSPRIVATE_GETALWAYSTRANSLATELANGUAGES = 1544,
   LANGUAGESETTINGSPRIVATE_SETLANGUAGEALWAYSTRANSLATESTATE = 1545,
+  ACCESSIBILITY_PRIVATE_SHOWCONFIRMATIONDIALOG = 1546,
   // Last entry: Add new entries above, then run:
   // python tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/third_party/closure_compiler/externs/accessibility_private.js b/third_party/closure_compiler/externs/accessibility_private.js
index d22548e..98ad90e6 100644
--- a/third_party/closure_compiler/externs/accessibility_private.js
+++ b/third_party/closure_compiler/externs/accessibility_private.js
@@ -458,6 +458,16 @@
 chrome.accessibilityPrivate.updateSelectToSpeakPanel = function(show, anchor, isPaused, speed) {};
 
 /**
+ * Shows a confirmation dialog.
+ * @param {string} title The title of the confirmation dialog.
+ * @param {string} description The description to show within the confirmation
+ *     dialog.
+ * @param {function(boolean): void} callback Called when the dialog is confirmed
+ *     or cancelled.
+ */
+chrome.accessibilityPrivate.showConfirmationDialog = function(title, description, callback) {};
+
+/**
  * Fired whenever ChromeVox should output introduction.
  * @type {!ChromeEvent}
  */
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index e3897c5d..289e674 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -26826,6 +26826,7 @@
       label="LANGUAGESETTINGSPRIVATE_GETALWAYSTRANSLATELANGUAGES"/>
   <int value="1545"
       label="LANGUAGESETTINGSPRIVATE_SETLANGUAGEALWAYSTRANSLATESTATE"/>
+  <int value="1546" label="ACCESSIBILITY_PRIVATE_SHOWCONFIRMATIONDIALOG"/>
 </enum>
 
 <enum name="ExtensionIconState">