Commit c31bf7f7 authored by Dana Fried's avatar Dana Fried Committed by Commit Bot

Create general announcement system for non-Mac screenreaders.

This uses a method suggested by aleventhal@ in that it provides a hidden
view on each widget that can be used to send accessibility notifications
via the existing (at least on Mac) ViewAccessibility::AnnounceText()
method.

This CL expands AnnounceText() to be available on all platforms, with
the non-Mac implementation using an invisible view parented to the
widget's RootView.

Currently uses alerts since they are compatible with all screen readers.
Followup work will be to change to a live region update, which is a less
obtrusive alert (see crbug.com/1024898).

Bug: 1024026
Change-Id: I9dbe0d7e38fb69d3ada8b8e1574aee211bfe6291
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1913281Reviewed-by: default avatarRobert Liao <robliao@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Reviewed-by: default avatarAaron Leventhal <aleventhal@chromium.org>
Reviewed-by: default avatarNektarios Paisios <nektar@chromium.org>
Commit-Queue: Robert Liao <robliao@chromium.org>
Auto-Submit: Dana Fried <dfried@chromium.org>
Cr-Commit-Position: refs/heads/master@{#717237}
parent 1ca2b3c4
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "ui/accessibility/platform/ax_platform_node.h" #include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/base/buildflags.h" #include "ui/base/buildflags.h"
#include "ui/views/view.h" #include "ui/views/view.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget.h" #include "ui/views/widget/widget.h"
namespace views { namespace views {
...@@ -291,6 +292,17 @@ gfx::NativeViewAccessible ViewAccessibility::GetNativeObject() { ...@@ -291,6 +292,17 @@ gfx::NativeViewAccessible ViewAccessibility::GetNativeObject() {
return nullptr; return nullptr;
} }
void ViewAccessibility::AnnounceText(const base::string16& text) {
Widget* const widget = view_->GetWidget();
if (!widget)
return;
auto* const root_view =
static_cast<internal::RootView*>(widget->GetRootView());
if (!root_view)
return;
root_view->AnnounceText(text);
}
gfx::NativeViewAccessible ViewAccessibility::GetFocusedDescendant() { gfx::NativeViewAccessible ViewAccessibility::GetFocusedDescendant() {
if (focused_virtual_child_) if (focused_virtual_child_)
return focused_virtual_child_->GetNativeObject(); return focused_virtual_child_->GetNativeObject();
......
...@@ -89,9 +89,10 @@ class VIEWS_EXPORT ViewAccessibility { ...@@ -89,9 +89,10 @@ class VIEWS_EXPORT ViewAccessibility {
virtual gfx::NativeViewAccessible GetNativeObject(); virtual gfx::NativeViewAccessible GetNativeObject();
virtual void NotifyAccessibilityEvent(ax::mojom::Event event_type) {} virtual void NotifyAccessibilityEvent(ax::mojom::Event event_type) {}
#if defined(OS_MACOSX)
virtual void AnnounceText(base::string16& text) {} // Causes the screen reader to announce |text|. If the current user is not
#endif // using a screen reader, has no effect.
virtual void AnnounceText(const base::string16& text);
virtual const ui::AXUniqueId& GetUniqueId() const; virtual const ui::AXUniqueId& GetUniqueId() const;
......
...@@ -191,7 +191,7 @@ void ViewAXPlatformNodeDelegate::NotifyAccessibilityEvent( ...@@ -191,7 +191,7 @@ void ViewAXPlatformNodeDelegate::NotifyAccessibilityEvent(
} }
#if defined(OS_MACOSX) #if defined(OS_MACOSX)
void ViewAXPlatformNodeDelegate::AnnounceText(base::string16& text) { void ViewAXPlatformNodeDelegate::AnnounceText(const base::string16& text) {
ax_platform_node_->AnnounceText(text); ax_platform_node_->AnnounceText(text);
} }
#endif #endif
......
...@@ -44,7 +44,7 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility, ...@@ -44,7 +44,7 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility,
gfx::NativeViewAccessible GetNativeObject() override; gfx::NativeViewAccessible GetNativeObject() override;
void NotifyAccessibilityEvent(ax::mojom::Event event_type) override; void NotifyAccessibilityEvent(ax::mojom::Event event_type) override;
#if defined(OS_MACOSX) #if defined(OS_MACOSX)
void AnnounceText(base::string16& text) override; void AnnounceText(const base::string16& text) override;
#endif #endif
// ui::AXPlatformNodeDelegate // ui::AXPlatformNodeDelegate
......
...@@ -9,6 +9,8 @@ jumbo_component("views_examples_lib") { ...@@ -9,6 +9,8 @@ jumbo_component("views_examples_lib") {
testonly = true testonly = true
sources = [ sources = [
"ax_example.cc",
"ax_example.h",
"box_layout_example.cc", "box_layout_example.cc",
"box_layout_example.h", "box_layout_example.h",
"bubble_example.cc", "bubble_example.cc",
......
// 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 "ui/views/examples/ax_example.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/view_class_properties.h"
namespace views {
namespace examples {
AxExample::AxExample() : ExampleBase("Accessibility Features") {}
AxExample::~AxExample() = default;
void AxExample::CreateExampleView(View* container) {
container->SetBackground(CreateSolidBackground(SK_ColorWHITE));
FlexLayout* const layout =
container->SetLayoutManager(std::make_unique<FlexLayout>());
layout->SetCollapseMargins(true);
layout->SetOrientation(LayoutOrientation::kVertical);
layout->SetDefault(kMarginsKey, gfx::Insets(10));
layout->SetMainAxisAlignment(LayoutAlignment::kStart);
layout->SetCrossAxisAlignment(LayoutAlignment::kStart);
announce_button_ = container->AddChildView(
MdTextButton::Create(this, base::ASCIIToUTF16("AnnounceText")));
}
void AxExample::ButtonPressed(Button* sender, const ui::Event& event) {
sender->GetViewAccessibility().AnnounceText(
base::ASCIIToUTF16("Button pressed."));
}
} // namespace examples
} // namespace views
// 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 UI_VIEWS_EXAMPLES_AX_EXAMPLE_H_
#define UI_VIEWS_EXAMPLES_AX_EXAMPLE_H_
#include "base/macros.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/examples/example_base.h"
namespace views {
namespace examples {
// ButtonExample simply counts the number of clicks.
class VIEWS_EXAMPLES_EXPORT AxExample : public ExampleBase,
public ButtonListener {
public:
AxExample();
~AxExample() override;
// ExampleBase:
void CreateExampleView(View* container) override;
private:
// ButtonListener:
void ButtonPressed(Button* sender, const ui::Event& event) override;
Button* announce_button_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(AxExample);
};
} // namespace examples
} // namespace views
#endif // UI_VIEWS_EXAMPLES_AX_EXAMPLE_H_
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "ui/views/background.h" #include "ui/views/background.h"
#include "ui/views/controls/combobox/combobox.h" #include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/label.h" #include "ui/views/controls/label.h"
#include "ui/views/examples/ax_example.h"
#include "ui/views/examples/box_layout_example.h" #include "ui/views/examples/box_layout_example.h"
#include "ui/views/examples/bubble_example.h" #include "ui/views/examples/bubble_example.h"
#include "ui/views/examples/button_example.h" #include "ui/views/examples/button_example.h"
...@@ -61,6 +62,7 @@ namespace { ...@@ -61,6 +62,7 @@ namespace {
// Creates the default set of examples. // Creates the default set of examples.
ExampleVector CreateExamples() { ExampleVector CreateExamples() {
ExampleVector examples; ExampleVector examples;
examples.push_back(std::make_unique<AxExample>());
examples.push_back(std::make_unique<BoxLayoutExample>()); examples.push_back(std::make_unique<BoxLayoutExample>());
examples.push_back(std::make_unique<BubbleExample>()); examples.push_back(std::make_unique<BubbleExample>());
examples.push_back(std::make_unique<ButtonExample>()); examples.push_back(std::make_unique<ButtonExample>());
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/macros.h" #include "base/macros.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h" #include "ui/accessibility/ax_node_data.h"
#include "ui/base/cursor/cursor.h" #include "ui/base/cursor/cursor.h"
#include "ui/base/dragdrop/drag_drop_types.h" #include "ui/base/dragdrop/drag_drop_types.h"
...@@ -48,6 +49,37 @@ class MouseEnterExitEvent : public ui::MouseEvent { ...@@ -48,6 +49,37 @@ class MouseEnterExitEvent : public ui::MouseEvent {
} // namespace } // namespace
// Used by RootView to create a hidden child that can be used to make screen
// reader announcements on platforms that don't have a reliable system API call
// to do that.
//
// We use a separate view because the RootView itself supplies the widget's
// accessible name and cannot serve double-duty (the inability for views to make
// their own announcements without changing their accessible name or description
// is the reason this system exists at all).
class AnnounceTextView : public View {
public:
~AnnounceTextView() override = default;
void Announce(const base::string16& text) {
// TODO(crbug.com/1024898): Use kLiveRegionChanged when supported across
// screen readers and platforms. See bug for details.
announce_text_ = text;
NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
}
// View:
void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
// TODO(crbug.com/1024898): Use live regions (do not use alerts).
// May require setting kLiveStatus, kContainerLiveStatus to "polite".
node_data->role = ax::mojom::Role::kAlert;
node_data->SetName(announce_text_);
}
private:
base::string16 announce_text_;
};
// This event handler receives events in the pre-target phase and takes care of // This event handler receives events in the pre-target phase and takes care of
// the following: // the following:
// - Shows keyboard-triggered context menus. // - Shows keyboard-triggered context menus.
...@@ -228,6 +260,21 @@ void RootView::DeviceScaleFactorChanged(float old_device_scale_factor, ...@@ -228,6 +260,21 @@ void RootView::DeviceScaleFactorChanged(float old_device_scale_factor,
new_device_scale_factor); new_device_scale_factor);
} }
// Accessibility ---------------------------------------------------------------
void RootView::AnnounceText(const base::string16& text) {
#if defined(OS_MACOSX)
// MacOSX has its own API for making announcements; see AnnounceText()
// override in ax_platform_node_mac.[h|mm]
NOTREACHED();
#else
DCHECK(GetWidget());
if (!announce_view_)
announce_view_ = AddChildView(std::make_unique<AnnounceTextView>());
announce_view_->Announce(text);
#endif
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// RootView, FocusTraversable implementation: // RootView, FocusTraversable implementation:
...@@ -600,6 +647,14 @@ void RootView::UpdateParentLayer() { ...@@ -600,6 +647,14 @@ void RootView::UpdateParentLayer() {
ReparentLayer(widget_->GetLayer()); ReparentLayer(widget_->GetLayer());
} }
void RootView::Layout() {
View::Layout();
// TODO(crbug.com/1026461): when FillLayout derives from LayoutManagerBase,
// just ignore the announce view instead of forcing it to zero size.
if (announce_view_)
announce_view_->SetSize({0, 0});
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// RootView, protected: // RootView, protected:
......
...@@ -28,6 +28,7 @@ class Widget; ...@@ -28,6 +28,7 @@ class Widget;
// This is a views-internal API and should not be used externally. // This is a views-internal API and should not be used externally.
// Widget exposes this object as a View*. // Widget exposes this object as a View*.
namespace internal { namespace internal {
class AnnounceTextView;
class PreEventDispatchHandler; class PreEventDispatchHandler;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
...@@ -92,6 +93,11 @@ class VIEWS_EXPORT RootView : public View, ...@@ -92,6 +93,11 @@ class VIEWS_EXPORT RootView : public View,
void DeviceScaleFactorChanged(float old_device_scale_factor, void DeviceScaleFactorChanged(float old_device_scale_factor,
float new_device_scale_factor); float new_device_scale_factor);
// Accessibility -------------------------------------------------------------
// Make an announcement through the screen reader, if present.
void AnnounceText(const base::string16& text);
// Overridden from FocusTraversable: // Overridden from FocusTraversable:
FocusSearch* GetFocusSearch() override; FocusSearch* GetFocusSearch() override;
FocusTraversable* GetFocusTraversableParent() override; FocusTraversable* GetFocusTraversableParent() override;
...@@ -117,6 +123,7 @@ class VIEWS_EXPORT RootView : public View, ...@@ -117,6 +123,7 @@ class VIEWS_EXPORT RootView : public View,
void SetMouseHandler(View* new_mouse_handler) override; void SetMouseHandler(View* new_mouse_handler) override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override; void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
void UpdateParentLayer() override; void UpdateParentLayer() override;
void Layout() override;
protected: protected:
// Overridden from View: // Overridden from View:
...@@ -233,6 +240,12 @@ class VIEWS_EXPORT RootView : public View, ...@@ -233,6 +240,12 @@ class VIEWS_EXPORT RootView : public View,
// Tracks drag state for a view. // Tracks drag state for a view.
View::DragInfo drag_info_; View::DragInfo drag_info_;
// Accessibility -------------------------------------------------------------
// Hidden view used to make announcements to the screen reader via an alert or
// live region update.
AnnounceTextView* announce_view_ = nullptr;
DISALLOW_IMPLICIT_CONSTRUCTORS(RootView); DISALLOW_IMPLICIT_CONSTRUCTORS(RootView);
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment