Commit aa1deea2 authored by Timothy Loh's avatar Timothy Loh Committed by Commit Bot

Output progress of views::ProgressBar in ChromeVox

This CL adds support for outputting the current progress of a view
progress bar by ChromeVox, which currently says "progress indicator"
without indicating the actual progress.

The logic here is mostly copied from views::Slider.

Bug: 1021414
Change-Id: I9a31708d34b9823ed72a5b9f44b9b08de4df95a2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2129367Reviewed-by: default avatarTrent Apted <tapted@chromium.org>
Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Commit-Queue: Timothy Loh <timloh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#758668}
parent ae56bf66
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include "base/i18n/number_formatting.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/macros.h" #include "base/macros.h"
#include "cc/paint/paint_flags.h" #include "cc/paint/paint_flags.h"
...@@ -21,6 +22,7 @@ ...@@ -21,6 +22,7 @@
#include "ui/gfx/color_utils.h" #include "ui/gfx/color_utils.h"
#include "ui/native_theme/native_theme.h" #include "ui/native_theme/native_theme.h"
#include "ui/views/metadata/metadata_impl_macros.h" #include "ui/views/metadata/metadata_impl_macros.h"
#include "ui/views/widget/widget.h"
namespace views { namespace views {
...@@ -42,6 +44,10 @@ void AddPossiblyRoundRectToPath(const gfx::Rect& rectangle, ...@@ -42,6 +44,10 @@ void AddPossiblyRoundRectToPath(const gfx::Rect& rectangle,
} }
} }
int RoundToPercent(double fractional_value) {
return static_cast<int>(fractional_value * 100);
}
} // namespace } // namespace
ProgressBar::ProgressBar(int preferred_height, bool allow_round_corner) ProgressBar::ProgressBar(int preferred_height, bool allow_round_corner)
...@@ -54,6 +60,10 @@ ProgressBar::~ProgressBar() = default; ...@@ -54,6 +60,10 @@ ProgressBar::~ProgressBar() = default;
void ProgressBar::GetAccessibleNodeData(ui::AXNodeData* node_data) { void ProgressBar::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kProgressIndicator; node_data->role = ax::mojom::Role::kProgressIndicator;
if (IsIndeterminate())
node_data->RemoveStringAttribute(ax::mojom::StringAttribute::kValue);
else
node_data->SetValue(base::FormatPercent(RoundToPercent(current_value_)));
} }
gfx::Size ProgressBar::CalculatePreferredSize() const { gfx::Size ProgressBar::CalculatePreferredSize() const {
...@@ -64,6 +74,14 @@ gfx::Size ProgressBar::CalculatePreferredSize() const { ...@@ -64,6 +74,14 @@ gfx::Size ProgressBar::CalculatePreferredSize() const {
return pref_size; return pref_size;
} }
void ProgressBar::VisibilityChanged(View* starting_from, bool is_visible) {
MaybeNotifyAccessibilityValueChanged();
}
void ProgressBar::AddedToWidget() {
MaybeNotifyAccessibilityValueChanged();
}
void ProgressBar::OnPaint(gfx::Canvas* canvas) { void ProgressBar::OnPaint(gfx::Canvas* canvas) {
if (IsIndeterminate()) if (IsIndeterminate())
return OnPaintIndeterminate(canvas); return OnPaintIndeterminate(canvas);
...@@ -117,6 +135,8 @@ void ProgressBar::SetValue(double value) { ...@@ -117,6 +135,8 @@ void ProgressBar::SetValue(double value) {
indeterminate_bar_animation_.reset(); indeterminate_bar_animation_.reset();
OnPropertyChanged(&current_value_, kPropertyEffectsPaint); OnPropertyChanged(&current_value_, kPropertyEffectsPaint);
} }
MaybeNotifyAccessibilityValueChanged();
} }
SkColor ProgressBar::GetForegroundColor() const { SkColor ProgressBar::GetForegroundColor() const {
...@@ -227,6 +247,15 @@ void ProgressBar::OnPaintIndeterminate(gfx::Canvas* canvas) { ...@@ -227,6 +247,15 @@ void ProgressBar::OnPaintIndeterminate(gfx::Canvas* canvas) {
canvas->DrawPath(slice_path, slice_flags); canvas->DrawPath(slice_path, slice_flags);
} }
void ProgressBar::MaybeNotifyAccessibilityValueChanged() {
if (!GetWidget() || !GetWidget()->IsVisible() ||
RoundToPercent(current_value_) == last_announced_percentage_) {
return;
}
last_announced_percentage_ = RoundToPercent(current_value_);
NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
}
BEGIN_METADATA(ProgressBar) BEGIN_METADATA(ProgressBar)
METADATA_PARENT_CLASS(View) METADATA_PARENT_CLASS(View)
ADD_PROPERTY_METADATA(ProgressBar, SkColor, ForegroundColor) ADD_PROPERTY_METADATA(ProgressBar, SkColor, ForegroundColor)
......
...@@ -30,9 +30,11 @@ class VIEWS_EXPORT ProgressBar : public View, public gfx::AnimationDelegate { ...@@ -30,9 +30,11 @@ class VIEWS_EXPORT ProgressBar : public View, public gfx::AnimationDelegate {
bool allow_round_corner = true); bool allow_round_corner = true);
~ProgressBar() override; ~ProgressBar() override;
// Overridden from View: // View:
void GetAccessibleNodeData(ui::AXNodeData* node_data) override; void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
gfx::Size CalculatePreferredSize() const override; gfx::Size CalculatePreferredSize() const override;
void VisibilityChanged(View* starting_from, bool is_visible) override;
void AddedToWidget() override;
void OnPaint(gfx::Canvas* canvas) override; void OnPaint(gfx::Canvas* canvas) override;
double GetValue() const; double GetValue() const;
...@@ -59,6 +61,9 @@ class VIEWS_EXPORT ProgressBar : public View, public gfx::AnimationDelegate { ...@@ -59,6 +61,9 @@ class VIEWS_EXPORT ProgressBar : public View, public gfx::AnimationDelegate {
bool IsIndeterminate(); bool IsIndeterminate();
void OnPaintIndeterminate(gfx::Canvas* canvas); void OnPaintIndeterminate(gfx::Canvas* canvas);
// Fire an accessibility event if visible and the progress has changed.
void MaybeNotifyAccessibilityValueChanged();
// Current progress to display, should be in the range 0.0 to 1.0. // Current progress to display, should be in the range 0.0 to 1.0.
double current_value_ = 0.0; double current_value_ = 0.0;
...@@ -72,6 +77,8 @@ class VIEWS_EXPORT ProgressBar : public View, public gfx::AnimationDelegate { ...@@ -72,6 +77,8 @@ class VIEWS_EXPORT ProgressBar : public View, public gfx::AnimationDelegate {
std::unique_ptr<gfx::LinearAnimation> indeterminate_bar_animation_; std::unique_ptr<gfx::LinearAnimation> indeterminate_bar_animation_;
int last_announced_percentage_ = -1;
DISALLOW_COPY_AND_ASSIGN(ProgressBar); DISALLOW_COPY_AND_ASSIGN(ProgressBar);
}; };
......
...@@ -8,38 +8,122 @@ ...@@ -8,38 +8,122 @@
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h" #include "ui/accessibility/ax_node_data.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/color_utils.h" #include "ui/gfx/color_utils.h"
#include "ui/native_theme/native_theme.h" #include "ui/native_theme/native_theme.h"
#include "ui/views/accessibility/ax_event_manager.h"
#include "ui/views/accessibility/ax_event_observer.h"
#include "ui/views/test/views_test_base.h" #include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget_utils.h"
namespace views { namespace views {
using ProgressBarTest = ViewsTestBase; namespace {
TEST_F(ProgressBarTest, Accessibility) { class TestAXEventObserver : public AXEventObserver {
ProgressBar bar; public:
bar.SetValue(0.62); TestAXEventObserver() { AXEventManager::Get()->AddObserver(this); }
~TestAXEventObserver() override {
AXEventManager::Get()->RemoveObserver(this);
}
TestAXEventObserver(const TestAXEventObserver&) = delete;
TestAXEventObserver& operator=(const TestAXEventObserver&) = delete;
int value_changed_count() const { return value_changed_count_; }
// AXEventObserver:
void OnViewEvent(View* view, ax::mojom::Event event_type) override {
if (event_type == ax::mojom::Event::kValueChanged)
value_changed_count_++;
}
private:
int value_changed_count_ = 0;
};
} // namespace
class ProgressBarTest : public ViewsTestBase {
protected:
// ViewsTestBase:
void SetUp() override {
ViewsTestBase::SetUp();
bar_ = new ProgressBar;
widget_ = CreateTestWidget();
widget_->SetContentsView(bar_);
widget_->Show();
event_generator_ = std::make_unique<ui::test::EventGenerator>(
GetRootWindow(widget_.get()));
}
void TearDown() override {
widget_.reset();
ViewsTestBase::TearDown();
}
ProgressBar* bar_;
std::unique_ptr<Widget> widget_;
std::unique_ptr<ui::test::EventGenerator> event_generator_;
};
TEST_F(ProgressBarTest, AccessibleNodeData) {
bar_->SetValue(0.626);
ui::AXNodeData node_data; ui::AXNodeData node_data;
bar.GetAccessibleNodeData(&node_data); bar_->GetAccessibleNodeData(&node_data);
EXPECT_EQ(ax::mojom::Role::kProgressIndicator, node_data.role); EXPECT_EQ(ax::mojom::Role::kProgressIndicator, node_data.role);
EXPECT_EQ(base::string16(), EXPECT_EQ(base::string16(),
node_data.GetString16Attribute(ax::mojom::StringAttribute::kName)); node_data.GetString16Attribute(ax::mojom::StringAttribute::kName));
EXPECT_EQ(std::string("62%"),
node_data.GetStringAttribute(ax::mojom::StringAttribute::kValue));
EXPECT_FALSE( EXPECT_FALSE(
node_data.HasIntAttribute(ax::mojom::IntAttribute::kRestriction)); node_data.HasIntAttribute(ax::mojom::IntAttribute::kRestriction));
} }
// Verifies the correct a11y events are raised for an accessible progress bar.
TEST_F(ProgressBarTest, AccessibilityEvents) {
TestAXEventObserver observer;
EXPECT_EQ(0, observer.value_changed_count());
bar_->SetValue(0.50);
EXPECT_EQ(1, observer.value_changed_count());
bar_->SetValue(0.63);
EXPECT_EQ(2, observer.value_changed_count());
bar_->SetValue(0.636);
EXPECT_EQ(2, observer.value_changed_count());
bar_->SetValue(0.642);
EXPECT_EQ(3, observer.value_changed_count());
widget_->Hide();
widget_->Show();
EXPECT_EQ(3, observer.value_changed_count());
widget_->Hide();
bar_->SetValue(0.8);
EXPECT_EQ(3, observer.value_changed_count());
widget_->Show();
EXPECT_EQ(4, observer.value_changed_count());
}
// Test that default colors can be overridden. Used by Chromecast. // Test that default colors can be overridden. Used by Chromecast.
TEST_F(ProgressBarTest, OverrideDefaultColors) { TEST_F(ProgressBarTest, OverrideDefaultColors) {
ProgressBar bar; EXPECT_NE(SK_ColorRED, bar_->GetForegroundColor());
EXPECT_NE(SK_ColorRED, bar.GetForegroundColor()); EXPECT_NE(SK_ColorGREEN, bar_->GetBackgroundColor());
EXPECT_NE(SK_ColorGREEN, bar.GetBackgroundColor()); EXPECT_NE(bar_->GetForegroundColor(), bar_->GetBackgroundColor());
EXPECT_NE(bar.GetForegroundColor(), bar.GetBackgroundColor());
bar_->SetForegroundColor(SK_ColorRED);
bar.SetForegroundColor(SK_ColorRED); bar_->SetBackgroundColor(SK_ColorGREEN);
bar.SetBackgroundColor(SK_ColorGREEN); EXPECT_EQ(SK_ColorRED, bar_->GetForegroundColor());
EXPECT_EQ(SK_ColorRED, bar.GetForegroundColor()); EXPECT_EQ(SK_ColorGREEN, bar_->GetBackgroundColor());
EXPECT_EQ(SK_ColorGREEN, bar.GetBackgroundColor());
} }
} // namespace views } // namespace views
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