Add an accessible describedby relation to bubble dialogs.

The JAWS screen reader is not announcing the text of a
bubble dialog when it appears unless the user presses
a hot key to read the whole dialog. This can be mitigated
by adding a "describedby" relation between the dialog and
the main label text.

Bug: 953325
Change-Id: I8f9a68a80d7d7e91ea51f2816a3d85e312e93b39
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1582844
Reviewed-by: Elly Fong-Jones <ellyjones@chromium.org>
Reviewed-by: Aaron Leventhal <aleventhal@chromium.org>
Commit-Queue: Dominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#657371}
diff --git a/chrome/browser/ui/views/confirm_bubble_views.cc b/chrome/browser/ui/views/confirm_bubble_views.cc
index 8a28dfa..c14aa70 100644
--- a/chrome/browser/ui/views/confirm_bubble_views.cc
+++ b/chrome/browser/ui/views/confirm_bubble_views.cc
@@ -13,8 +13,10 @@
 #include "components/constrained_window/constrained_window_views.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/vector_icons/vector_icons.h"
+#include "ui/accessibility/ax_node_data.h"
 #include "ui/base/buildflags.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/button/image_button_factory.h"
 #include "ui/views/controls/label.h"
@@ -37,13 +39,14 @@
                 kMaxMessageWidth, false);
 
   // Add the message label.
-  views::Label* label = new views::Label(model_->GetMessageText());
+  auto label = std::make_unique<views::Label>(model_->GetMessageText());
+  label_ = label.get();
   DCHECK(!label->text().empty());
   label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   label->SetMultiLine(true);
   label->SizeToFit(kMaxMessageWidth);
   layout->StartRow(views::GridLayout::kFixedSize, 0);
-  layout->AddView(label);
+  layout->AddView(label.release());
 
   chrome::RecordDialogCreation(chrome::DialogIdentifier::CONFIRM_BUBBLE);
 }
@@ -115,6 +118,15 @@
   }
 }
 
+void ConfirmBubbleViews::ViewHierarchyChanged(
+    const views::ViewHierarchyChangedDetails& details) {
+  if (details.is_add && details.child == this && GetWidget()) {
+    GetWidget()->GetRootView()->GetViewAccessibility().OverrideDescribedBy(
+        label_);
+  }
+  DialogDelegateView::ViewHierarchyChanged(details);
+}
+
 namespace chrome {
 
 void ShowConfirmBubble(gfx::NativeWindow window,
diff --git a/chrome/browser/ui/views/confirm_bubble_views.h b/chrome/browser/ui/views/confirm_bubble_views.h
index 4b160b6..decea20 100644
--- a/chrome/browser/ui/views/confirm_bubble_views.h
+++ b/chrome/browser/ui/views/confirm_bubble_views.h
@@ -16,7 +16,8 @@
 
 namespace views {
 class ImageButton;
-}
+class Label;
+}  // namespace views
 
 // A dialog (with the standard Title/[OK]/[Cancel] UI elements), as well as
 // a message Label and help (?) button. The dialog ultimately appears like this:
@@ -50,10 +51,15 @@
   // views::ButtonListener implementation.
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
 
+  // views::View implementation.
+  void ViewHierarchyChanged(
+      const views::ViewHierarchyChangedDetails& details) override;
+
  private:
   // The model to customize this bubble view.
   std::unique_ptr<ConfirmBubbleModel> model_;
 
+  views::Label* label_;
   views::ImageButton* help_button_;
 
   DISALLOW_COPY_AND_ASSIGN(ConfirmBubbleViews);
diff --git a/ui/views/accessibility/view_accessibility.cc b/ui/views/accessibility/view_accessibility.cc
index 2f2ea9c..943d59e 100644
--- a/ui/views/accessibility/view_accessibility.cc
+++ b/ui/views/accessibility/view_accessibility.cc
@@ -166,12 +166,20 @@
       ax::mojom::IntAttribute::kPosInSet,
       ax::mojom::IntAttribute::kSetSize,
   };
-
   for (auto attribute : kOverridableIntAttributes) {
     if (custom_data_.HasIntAttribute(attribute))
       data->AddIntAttribute(attribute, custom_data_.GetIntAttribute(attribute));
   }
 
+  static const ax::mojom::IntListAttribute kOverridableIntListAttributes[]{
+      ax::mojom::IntListAttribute::kDescribedbyIds,
+  };
+  for (auto attribute : kOverridableIntListAttributes) {
+    if (custom_data_.HasIntListAttribute(attribute))
+      data->AddIntListAttribute(attribute,
+                                custom_data_.GetIntListAttribute(attribute));
+  }
+
   if (!data->HasStringAttribute(ax::mojom::StringAttribute::kDescription)) {
     base::string16 tooltip = view_->GetTooltipText(gfx::Point());
     // Some screen readers announce the accessible description right after the
@@ -244,6 +252,13 @@
   custom_data_.relative_bounds.bounds = bounds;
 }
 
+void ViewAccessibility::OverrideDescribedBy(View* described_by_view) {
+  int described_by_id =
+      described_by_view->GetViewAccessibility().GetUniqueId().Get();
+  custom_data_.AddIntListAttribute(ax::mojom::IntListAttribute::kDescribedbyIds,
+                                   {described_by_id});
+}
+
 void ViewAccessibility::OverridePosInSet(int pos_in_set, int set_size) {
   custom_data_.AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, pos_in_set);
   custom_data_.AddIntAttribute(ax::mojom::IntAttribute::kSetSize, set_size);
diff --git a/ui/views/accessibility/view_accessibility.h b/ui/views/accessibility/view_accessibility.h
index 35f9970..866305b 100644
--- a/ui/views/accessibility/view_accessibility.h
+++ b/ui/views/accessibility/view_accessibility.h
@@ -66,6 +66,7 @@
   void OverrideIsLeaf(bool value);
   void OverrideIsIgnored(bool value);
   void OverrideBounds(const gfx::RectF& bounds);
+  void OverrideDescribedBy(View* described_by_view);
 
   // Override indexes used by some screen readers when describing elements in a
   // menu, list, etc. If not specified, a view's index in its parent and its