Commit e4924764 authored by Toni Barzic's avatar Toni Barzic Committed by Commit Bot

Improve app list search accessibility

Makes search result page view a list box, whose value indicates the
number of available results, and the query for which the results are
shown. The search result view roles are changed to list box options
(which should give the user feedback of the relative position of the
currently selected result).

When the result list is updated, the accessibility framework is notified
of the results page view's value change (so the new value gets announced
by spoken feedback). The notification is sent with a delay, once the set
of results, and the query itself stabilize to avoid spamming accessibility
framework with interim values.

The result selection changes are still announced by sending kSelection
events on the selected result, but the timing of the notification is
now controlled by the search results page view. The notification is not
sent from the views when the selection state changes - instead,
SearchResultsPageView will request the selected result view to send
notification when required. With this, selection change notifications are
throttled while the result list is still changing.


Bug: 939111, 1021270
Change-Id: I0db6f1263e0bea0dddd0f278436bfc025bc6f450
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1925769
Commit-Queue: Toni Baržić <tbarzic@chromium.org>
Reviewed-by: default avatarJenny Zhang <jennyz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#718405}
parent fab4c847
...@@ -103,17 +103,15 @@ void ContentsView::Init(AppListModel* model) { ...@@ -103,17 +103,15 @@ void ContentsView::Init(AppListModel* model) {
AddLauncherPage(horizontal_page_container_, ash::AppListState::kStateApps); AddLauncherPage(horizontal_page_container_, ash::AppListState::kStateApps);
// Search results UI. // Search results UI.
search_results_page_view_ = new SearchResultPageView(view_delegate); search_results_page_view_ =
new SearchResultPageView(view_delegate, view_delegate->GetSearchModel());
// Search result containers.
SearchModel::SearchResults* results =
view_delegate->GetSearchModel()->results();
// Search result containers:
if (app_list_features::IsAnswerCardEnabled()) { if (app_list_features::IsAnswerCardEnabled()) {
search_result_answer_card_view_ = search_result_answer_card_view_ =
new SearchResultAnswerCardView(view_delegate); new SearchResultAnswerCardView(view_delegate);
search_results_page_view_->AddSearchResultContainerView( search_results_page_view_->AddSearchResultContainerView(
results, search_result_answer_card_view_); search_result_answer_card_view_);
} }
expand_arrow_view_ = new ExpandArrowView(this, app_list_view_); expand_arrow_view_ = new ExpandArrowView(this, app_list_view_);
...@@ -123,12 +121,12 @@ void ContentsView::Init(AppListModel* model) { ...@@ -123,12 +121,12 @@ void ContentsView::Init(AppListModel* model) {
search_results_page_view_, GetSearchBoxView()->search_box(), search_results_page_view_, GetSearchBoxView()->search_box(),
view_delegate); view_delegate);
search_results_page_view_->AddSearchResultContainerView( search_results_page_view_->AddSearchResultContainerView(
results, search_result_tile_item_list_view_); search_result_tile_item_list_view_);
search_result_list_view_ = search_result_list_view_ =
new SearchResultListView(GetAppListMainView(), view_delegate); new SearchResultListView(GetAppListMainView(), view_delegate);
search_results_page_view_->AddSearchResultContainerView( search_results_page_view_->AddSearchResultContainerView(
results, search_result_list_view_); search_result_list_view_);
AddLauncherPage(search_results_page_view_, AddLauncherPage(search_results_page_view_,
ash::AppListState::kStateSearchResults); ash::AppListState::kStateSearchResults);
......
...@@ -141,6 +141,7 @@ ResultSelectionController::GetNextResultLocationForLocation( ...@@ -141,6 +141,7 @@ ResultSelectionController::GetNextResultLocationForLocation(
if (selected_result_ && event.key_code() == ui::VKEY_TAB && if (selected_result_ && event.key_code() == ui::VKEY_TAB &&
selected_result_->SelectNextResultAction(event.IsShiftDown())) { selected_result_->SelectNextResultAction(event.IsShiftDown())) {
selection_change_callback_.Run();
return MoveResult::kNone; return MoveResult::kNone;
} }
......
...@@ -54,7 +54,6 @@ class TestResultViewWithActions : public TestResultView, ...@@ -54,7 +54,6 @@ class TestResultViewWithActions : public TestResultView,
// SearchResultActionsViewDelegate: // SearchResultActionsViewDelegate:
void OnSearchResultActionActivated(size_t index, int event_flags) override {} void OnSearchResultActionActivated(size_t index, int event_flags) override {}
void OnSearchResultActionsUnSelected() override {}
bool IsSearchResultHoveredOrSelected() override { return selected(); } bool IsSearchResultHoveredOrSelected() override { return selected(); }
SearchResultActionsView* GetActionsView() { SearchResultActionsView* GetActionsView() {
...@@ -470,7 +469,7 @@ class ResultSelectionTest : public testing::Test, ...@@ -470,7 +469,7 @@ class ResultSelectionTest : public testing::Test,
// expected to change. // expected to change.
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(tab_key_)); result_selection_controller_->MoveSelection(tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(1, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(0, 0), GetCurrentLocation()); ASSERT_EQ(create_test_location(0, 0), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionSelected(0)); EXPECT_TRUE(CurrentResultActionSelected(0));
...@@ -479,7 +478,7 @@ class ResultSelectionTest : public testing::Test, ...@@ -479,7 +478,7 @@ class ResultSelectionTest : public testing::Test,
// expected to change. // expected to change.
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(tab_key_)); result_selection_controller_->MoveSelection(tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(1, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(0, 0), GetCurrentLocation()); ASSERT_EQ(create_test_location(0, 0), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionSelected(1)); EXPECT_TRUE(CurrentResultActionSelected(1));
...@@ -516,7 +515,7 @@ class ResultSelectionTest : public testing::Test, ...@@ -516,7 +515,7 @@ class ResultSelectionTest : public testing::Test,
// TAB - stay at the same result, but select next action. // TAB - stay at the same result, but select next action.
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(tab_key_)); result_selection_controller_->MoveSelection(tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(1, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation()); ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionSelected(0)); EXPECT_TRUE(CurrentResultActionSelected(0));
...@@ -524,7 +523,7 @@ class ResultSelectionTest : public testing::Test, ...@@ -524,7 +523,7 @@ class ResultSelectionTest : public testing::Test,
// Shift-TAB - same result, but deselects actions. // Shift-TAB - same result, but deselects actions.
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(shift_tab_key_)); result_selection_controller_->MoveSelection(shift_tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(1, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation()); ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionNotSelected()); EXPECT_TRUE(CurrentResultActionNotSelected());
...@@ -532,7 +531,7 @@ class ResultSelectionTest : public testing::Test, ...@@ -532,7 +531,7 @@ class ResultSelectionTest : public testing::Test,
// TAB - reselect the first action. // TAB - reselect the first action.
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(tab_key_)); result_selection_controller_->MoveSelection(tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(1, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation()); ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionSelected(0)); EXPECT_TRUE(CurrentResultActionSelected(0));
...@@ -540,7 +539,7 @@ class ResultSelectionTest : public testing::Test, ...@@ -540,7 +539,7 @@ class ResultSelectionTest : public testing::Test,
// TAB - select the next action. // TAB - select the next action.
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(tab_key_)); result_selection_controller_->MoveSelection(tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(1, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation()); ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionSelected(1)); EXPECT_TRUE(CurrentResultActionSelected(1));
...@@ -568,7 +567,7 @@ class ResultSelectionTest : public testing::Test, ...@@ -568,7 +567,7 @@ class ResultSelectionTest : public testing::Test,
// Shift-TAB - move to previous action. // Shift-TAB - move to previous action.
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(shift_tab_key_)); result_selection_controller_->MoveSelection(shift_tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(1, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation()); ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionSelected(0)); EXPECT_TRUE(CurrentResultActionSelected(0));
...@@ -873,7 +872,7 @@ TEST_F(ResultSelectionTest, TabCycleInContainerWithResultActions) { ...@@ -873,7 +872,7 @@ TEST_F(ResultSelectionTest, TabCycleInContainerWithResultActions) {
// expected to change. // expected to change.
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(tab_key_)); result_selection_controller_->MoveSelection(tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(1, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(0, 0), GetCurrentLocation()); ASSERT_EQ(create_test_location(0, 0), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionSelected(0)); EXPECT_TRUE(CurrentResultActionSelected(0));
...@@ -891,7 +890,7 @@ TEST_F(ResultSelectionTest, TabCycleInContainerWithResultActions) { ...@@ -891,7 +890,7 @@ TEST_F(ResultSelectionTest, TabCycleInContainerWithResultActions) {
// TAB - next action selected. // TAB - next action selected.
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(tab_key_)); result_selection_controller_->MoveSelection(tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(1, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(1, 0), GetCurrentLocation()); ASSERT_EQ(create_test_location(1, 0), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionSelected(0)); EXPECT_TRUE(CurrentResultActionSelected(0));
...@@ -972,7 +971,7 @@ TEST_F(ResultSelectionTest, TabCycleInContainerSingleResultWithActionUsingTab) { ...@@ -972,7 +971,7 @@ TEST_F(ResultSelectionTest, TabCycleInContainerSingleResultWithActionUsingTab) {
// expected to change. // expected to change.
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(tab_key_)); result_selection_controller_->MoveSelection(tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(1, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(0, 0), GetCurrentLocation()); ASSERT_EQ(create_test_location(0, 0), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionSelected(0)); EXPECT_TRUE(CurrentResultActionSelected(0));
...@@ -1055,7 +1054,7 @@ TEST_P(ResultSelectionTest, ...@@ -1055,7 +1054,7 @@ TEST_P(ResultSelectionTest,
// TAB to select an action. // TAB to select an action.
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(tab_key_)); result_selection_controller_->MoveSelection(tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(1, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(0, 0), GetCurrentLocation()); ASSERT_EQ(create_test_location(0, 0), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionSelected(0)); EXPECT_TRUE(CurrentResultActionSelected(0));
...@@ -1071,7 +1070,7 @@ TEST_P(ResultSelectionTest, ...@@ -1071,7 +1070,7 @@ TEST_P(ResultSelectionTest,
// TAB to select an action. // TAB to select an action.
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(tab_key_)); result_selection_controller_->MoveSelection(tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(1, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation()); ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionSelected(0)); EXPECT_TRUE(CurrentResultActionSelected(0));
...@@ -1112,7 +1111,7 @@ TEST_F(ResultSelectionTest, ...@@ -1112,7 +1111,7 @@ TEST_F(ResultSelectionTest,
// TAB to select an action. // TAB to select an action.
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(tab_key_)); result_selection_controller_->MoveSelection(tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(1, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(0, 0), GetCurrentLocation()); ASSERT_EQ(create_test_location(0, 0), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionSelected(0)); EXPECT_TRUE(CurrentResultActionSelected(0));
...@@ -1128,7 +1127,7 @@ TEST_F(ResultSelectionTest, ...@@ -1128,7 +1127,7 @@ TEST_F(ResultSelectionTest,
// TAB to select an action. // TAB to select an action.
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(tab_key_)); result_selection_controller_->MoveSelection(tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(1, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation()); ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionSelected(0)); EXPECT_TRUE(CurrentResultActionSelected(0));
...@@ -1168,7 +1167,7 @@ TEST_F(ResultSelectionTest, ResetWhileFirstResultActionSelected) { ...@@ -1168,7 +1167,7 @@ TEST_F(ResultSelectionTest, ResetWhileFirstResultActionSelected) {
// TAB to select an action. // TAB to select an action.
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(tab_key_)); result_selection_controller_->MoveSelection(tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(1, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(0, 0), GetCurrentLocation()); ASSERT_EQ(create_test_location(0, 0), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionSelected(0)); EXPECT_TRUE(CurrentResultActionSelected(0));
...@@ -1214,7 +1213,7 @@ TEST_F(ResultSelectionTest, ResetWhileResultActionSelected) { ...@@ -1214,7 +1213,7 @@ TEST_F(ResultSelectionTest, ResetWhileResultActionSelected) {
// TAB to select an action. // TAB to select an action.
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(tab_key_)); result_selection_controller_->MoveSelection(tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(1, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation()); ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionSelected(0)); EXPECT_TRUE(CurrentResultActionSelected(0));
...@@ -1264,7 +1263,7 @@ TEST_F(ResultSelectionTest, ActionRemovedWhileSelected) { ...@@ -1264,7 +1263,7 @@ TEST_F(ResultSelectionTest, ActionRemovedWhileSelected) {
result_selection_controller_->MoveSelection(tab_key_)); result_selection_controller_->MoveSelection(tab_key_));
EXPECT_EQ(ResultSelectionController::MoveResult::kNone, EXPECT_EQ(ResultSelectionController::MoveResult::kNone,
result_selection_controller_->MoveSelection(tab_key_)); result_selection_controller_->MoveSelection(tab_key_));
EXPECT_EQ(0, GetAndResetSelectionChangeCount()); EXPECT_EQ(3, GetAndResetSelectionChangeCount());
ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation()); ASSERT_EQ(create_test_location(0, 1), GetCurrentLocation());
EXPECT_TRUE(CurrentResultActionSelected(2)); EXPECT_TRUE(CurrentResultActionSelected(2));
......
...@@ -80,7 +80,6 @@ class SearchResultImageButton : public views::ImageButton { ...@@ -80,7 +80,6 @@ class SearchResultImageButton : public views::ImageButton {
SearchResultActionsView* parent_; SearchResultActionsView* parent_;
const bool visible_on_hover_; const bool visible_on_hover_;
bool to_be_activate_by_long_press_ = false; bool to_be_activate_by_long_press_ = false;
bool selected_ = false;
DISALLOW_COPY_AND_ASSIGN(SearchResultImageButton); DISALLOW_COPY_AND_ASSIGN(SearchResultImageButton);
}; };
...@@ -188,13 +187,6 @@ void SearchResultImageButton::UpdateOnStateChanged() { ...@@ -188,13 +187,6 @@ void SearchResultImageButton::UpdateOnStateChanged() {
SetVisible(parent_->IsSearchResultHoveredOrSelected() || SetVisible(parent_->IsSearchResultHoveredOrSelected() ||
parent()->Contains(GetFocusManager()->GetFocusedView())); parent()->Contains(GetFocusManager()->GetFocusedView()));
} }
const bool selected = parent_->GetSelectedAction() == tag();
if (selected_ != selected) {
selected_ = selected;
if (selected)
NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
}
} }
void SearchResultImageButton::OnPaintBackground(gfx::Canvas* canvas) { void SearchResultImageButton::OnPaintBackground(gfx::Canvas* canvas) {
...@@ -235,10 +227,8 @@ SearchResultActionsView::SearchResultActionsView( ...@@ -235,10 +227,8 @@ SearchResultActionsView::SearchResultActionsView(
SearchResultActionsView::~SearchResultActionsView() {} SearchResultActionsView::~SearchResultActionsView() {}
void SearchResultActionsView::SetActions(const SearchResult::Actions& actions) { void SearchResultActionsView::SetActions(const SearchResult::Actions& actions) {
if (selected_action_.has_value()) { if (selected_action_.has_value())
selected_action_.reset(); selected_action_.reset();
delegate_->OnSearchResultActionsUnSelected();
}
buttons_.clear(); buttons_.clear();
RemoveAllChildViews(true); RemoveAllChildViews(true);
...@@ -306,9 +296,20 @@ bool SearchResultActionsView::SelectNextAction(bool reverse_tab_order) { ...@@ -306,9 +296,20 @@ bool SearchResultActionsView::SelectNextAction(bool reverse_tab_order) {
return true; return true;
} }
void SearchResultActionsView::NotifyA11yResultSelected() {
DCHECK(HasSelectedAction());
int selected_action = GetSelectedAction();
for (views::Button* button : buttons_) {
if (button->tag() == selected_action) {
button->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
return;
}
}
}
void SearchResultActionsView::ClearSelectedAction() { void SearchResultActionsView::ClearSelectedAction() {
selected_action_.reset(); selected_action_.reset();
delegate_->OnSearchResultActionsUnSelected();
UpdateButtonsOnStateChanged(); UpdateButtonsOnStateChanged();
} }
......
...@@ -60,6 +60,9 @@ class APP_LIST_EXPORT SearchResultActionsView : public views::View, ...@@ -60,6 +60,9 @@ class APP_LIST_EXPORT SearchResultActionsView : public views::View,
// getting cleared). // getting cleared).
bool SelectNextAction(bool reverse_tab_order); bool SelectNextAction(bool reverse_tab_order);
// Sends kSelection a11y notification for the selected action button.
void NotifyA11yResultSelected();
// Clears selected action state. // Clears selected action state.
void ClearSelectedAction(); void ClearSelectedAction();
......
...@@ -15,11 +15,6 @@ class SearchResultActionsViewDelegate { ...@@ -15,11 +15,6 @@ class SearchResultActionsViewDelegate {
// in SearchResultActionsView. // in SearchResultActionsView.
virtual void OnSearchResultActionActivated(size_t index, int event_flags) = 0; virtual void OnSearchResultActionActivated(size_t index, int event_flags) = 0;
// Invoked when result action selection is cleared (it changes from the state
// where a result action is selected to the state where no actions are
// selected).
virtual void OnSearchResultActionsUnSelected() = 0;
// Returns true if the associated search result is hovered by mouse, or // Returns true if the associated search result is hovered by mouse, or
// or selected by keyboard. // or selected by keyboard.
virtual bool IsSearchResultHoveredOrSelected() = 0; virtual bool IsSearchResultHoveredOrSelected() = 0;
......
...@@ -60,6 +60,14 @@ bool SearchResultBaseView::SelectNextResultAction(bool reverse_tab_order) { ...@@ -60,6 +60,14 @@ bool SearchResultBaseView::SelectNextResultAction(bool reverse_tab_order) {
return true; return true;
} }
void SearchResultBaseView::NotifyA11yResultSelected() {
if (actions_view_ && actions_view_->HasSelectedAction()) {
actions_view_->NotifyA11yResultSelected();
return;
}
NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
}
void SearchResultBaseView::SetResult(SearchResult* result) { void SearchResultBaseView::SetResult(SearchResult* result) {
OnResultChanging(result); OnResultChanging(result);
ClearResult(); ClearResult();
...@@ -97,6 +105,7 @@ void SearchResultBaseView::UpdateAccessibleName() { ...@@ -97,6 +105,7 @@ void SearchResultBaseView::UpdateAccessibleName() {
void SearchResultBaseView::ClearResult() { void SearchResultBaseView::ClearResult() {
if (result_) if (result_)
result_->RemoveObserver(this); result_->RemoveObserver(this);
SetSelected(false, base::nullopt);
result_ = nullptr; result_ = nullptr;
} }
......
...@@ -39,6 +39,12 @@ class APP_LIST_EXPORT SearchResultBaseView : public views::Button, ...@@ -39,6 +39,12 @@ class APP_LIST_EXPORT SearchResultBaseView : public views::Button,
// Returns whether the selected result action was changed. // Returns whether the selected result action was changed.
bool SelectNextResultAction(bool reverse_tab_order); bool SelectNextResultAction(bool reverse_tab_order);
// If the search result is currently selected, sends the appropriate
// kSelection view accessibility event. For example, if a result action is
// selected, the notification will be sent for the selected action button
// view.
void NotifyA11yResultSelected();
SearchResult* result() const { return result_; } SearchResult* result() const { return result_; }
void SetResult(SearchResult* result); void SetResult(SearchResult* result);
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
#include "ash/public/cpp/view_shadow.h" #include "ash/public/cpp/view_shadow.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/search_box/search_box_constants.h" #include "ui/chromeos/search_box/search_box_constants.h"
#include "ui/compositor/layer.h" #include "ui/compositor/layer.h"
#include "ui/compositor/scoped_layer_animation_settings.h" #include "ui/compositor/scoped_layer_animation_settings.h"
...@@ -30,6 +32,8 @@ ...@@ -30,6 +32,8 @@
#include "ui/gfx/canvas.h" #include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h" #include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/insets.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h" #include "ui/views/background.h"
#include "ui/views/controls/scroll_view.h" #include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/scrollbar/overlay_scroll_bar.h" #include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
...@@ -66,6 +70,11 @@ constexpr SkColor kSeparatorColor = SkColorSetA(gfx::kGoogleGrey900, 0x24); ...@@ -66,6 +70,11 @@ constexpr SkColor kSeparatorColor = SkColorSetA(gfx::kGoogleGrey900, 0x24);
// The shadow elevation value for the shadow of the expanded search box. // The shadow elevation value for the shadow of the expanded search box.
constexpr int kSearchBoxSearchResultShadowElevation = 12; constexpr int kSearchBoxSearchResultShadowElevation = 12;
// The amount of time by which notifications to accessibility framework about
// result page changes are delayed.
constexpr base::TimeDelta kNotifyA11yDelay =
base::TimeDelta::FromMilliseconds(500);
// A container view that ensures the card background and the shadow are painted // A container view that ensures the card background and the shadow are painted
// in the correct order. // in the correct order.
class SearchCardView : public views::View { class SearchCardView : public views::View {
...@@ -152,8 +161,11 @@ class SearchResultPageView::HorizontalSeparator : public views::View { ...@@ -152,8 +161,11 @@ class SearchResultPageView::HorizontalSeparator : public views::View {
DISALLOW_COPY_AND_ASSIGN(HorizontalSeparator); DISALLOW_COPY_AND_ASSIGN(HorizontalSeparator);
}; };
SearchResultPageView::SearchResultPageView(AppListViewDelegate* view_delegate) SearchResultPageView::SearchResultPageView(AppListViewDelegate* view_delegate,
: view_delegate_(view_delegate), contents_view_(new views::View) { SearchModel* search_model)
: view_delegate_(view_delegate),
search_model_(search_model),
contents_view_(new views::View) {
SetPaintToLayer(); SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false); layer()->SetFillsBoundsOpaquely(false);
contents_view_->SetLayoutManager(std::make_unique<views::BoxLayout>( contents_view_->SetLayoutManager(std::make_unique<views::BoxLayout>(
...@@ -196,12 +208,13 @@ SearchResultPageView::SearchResultPageView(AppListViewDelegate* view_delegate) ...@@ -196,12 +208,13 @@ SearchResultPageView::SearchResultPageView(AppListViewDelegate* view_delegate)
&result_container_views_, &result_container_views_,
base::BindRepeating(&SearchResultPageView::SelectedResultChanged, base::BindRepeating(&SearchResultPageView::SelectedResultChanged,
base::Unretained(this))); base::Unretained(this)));
search_box_observer_.Add(search_model->search_box());
} }
SearchResultPageView::~SearchResultPageView() = default; SearchResultPageView::~SearchResultPageView() = default;
void SearchResultPageView::AddSearchResultContainerView( void SearchResultPageView::AddSearchResultContainerView(
SearchModel::SearchResults* results_model,
SearchResultContainerView* result_container) { SearchResultContainerView* result_container) {
if (!result_container_views_.empty()) { if (!result_container_views_.empty()) {
HorizontalSeparator* separator = new HorizontalSeparator(bounds().width()); HorizontalSeparator* separator = new HorizontalSeparator(bounds().width());
...@@ -210,7 +223,7 @@ void SearchResultPageView::AddSearchResultContainerView( ...@@ -210,7 +223,7 @@ void SearchResultPageView::AddSearchResultContainerView(
} }
contents_view_->AddChildView(new SearchCardView(result_container)); contents_view_->AddChildView(new SearchCardView(result_container));
result_container_views_.push_back(result_container); result_container_views_.push_back(result_container);
result_container->SetResults(results_model); result_container->SetResults(search_model_->results());
result_container->set_delegate(this); result_container->set_delegate(this);
} }
...@@ -278,6 +291,33 @@ void SearchResultPageView::OnBoundsChanged(const gfx::Rect& previous_bounds) { ...@@ -278,6 +291,33 @@ void SearchResultPageView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
layer()->SetClipRect(gfx::Rect()); layer()->SetClipRect(gfx::Rect());
} }
void SearchResultPageView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
if (!GetVisible())
return;
node_data->role = ax::mojom::Role::kListBox;
base::string16 value;
base::string16 query = search_model_->search_box()->text();
if (!query.empty()) {
if (last_search_result_count_ == 1) {
value = l10n_util::GetStringFUTF16(
IDS_APP_LIST_SEARCHBOX_RESULTS_ACCESSIBILITY_ANNOUNCEMENT_SINGLE_RESULT,
query);
} else {
value = l10n_util::GetStringFUTF16(
IDS_APP_LIST_SEARCHBOX_RESULTS_ACCESSIBILITY_ANNOUNCEMENT,
base::NumberToString16(last_search_result_count_), query);
}
} else {
value = l10n_util::GetStringFUTF16(
IDS_APP_LIST_SEARCHBOX_RESULTS_ACCESSIBILITY_ANNOUNCEMENT_ZERO_STATE,
base::NumberToString16(last_search_result_count_));
}
node_data->SetValue(value);
}
void SearchResultPageView::ReorderSearchResultContainers() { void SearchResultPageView::ReorderSearchResultContainers() {
int view_offset = 0; int view_offset = 0;
if (assistant_privacy_info_view_) { if (assistant_privacy_info_view_) {
...@@ -340,6 +380,47 @@ void SearchResultPageView::SelectedResultChanged() { ...@@ -340,6 +380,47 @@ void SearchResultPageView::SelectedResultChanged() {
} }
selected_row->ScrollViewToVisible(); selected_row->ScrollViewToVisible();
NotifySelectedResultChanged();
}
void SearchResultPageView::SetIgnoreResultChangesForA11y(bool ignore) {
if (ignore_result_changes_for_a11y_ == ignore)
return;
ignore_result_changes_for_a11y_ = ignore;
GetViewAccessibility().OverrideIsLeaf(ignore);
NotifyAccessibilityEvent(ax::mojom::Event::kTreeChanged, true);
}
void SearchResultPageView::ScheduleResultsChangedA11yNotification() {
if (!ignore_result_changes_for_a11y_) {
NotifyA11yResultsChanged();
return;
}
notify_a11y_results_changed_timer_.Start(
FROM_HERE, kNotifyA11yDelay,
base::BindOnce(&SearchResultPageView::NotifyA11yResultsChanged,
base::Unretained(this)));
}
void SearchResultPageView::NotifyA11yResultsChanged() {
SetIgnoreResultChangesForA11y(false);
NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
NotifySelectedResultChanged();
}
void SearchResultPageView::NotifySelectedResultChanged() {
if (ignore_result_changes_for_a11y_ ||
!result_selection_controller_->selected_location_details() ||
!result_selection_controller_->selected_result()) {
return;
}
NotifyAccessibilityEvent(ax::mojom::Event::kSelectedChildrenChanged, true);
result_selection_controller_->selected_result()->NotifyA11yResultSelected();
} }
void SearchResultPageView::OnSearchResultContainerResultsChanging() { void SearchResultPageView::OnSearchResultContainerResultsChanging() {
...@@ -347,20 +428,29 @@ void SearchResultPageView::OnSearchResultContainerResultsChanging() { ...@@ -347,20 +428,29 @@ void SearchResultPageView::OnSearchResultContainerResultsChanging() {
// The selection will be reset once the results are all updated. // The selection will be reset once the results are all updated.
if (app_list_features::IsSearchBoxSelectionEnabled()) if (app_list_features::IsSearchBoxSelectionEnabled())
result_selection_controller_->set_block_selection_changes(true); result_selection_controller_->set_block_selection_changes(true);
notify_a11y_results_changed_timer_.Stop();
SetIgnoreResultChangesForA11y(true);
} }
void SearchResultPageView::OnSearchResultContainerResultsChanged() { void SearchResultPageView::OnSearchResultContainerResultsChanged() {
DCHECK(!result_container_views_.empty()); DCHECK(!result_container_views_.empty());
DCHECK(result_container_views_.size() == separators_.size() + 1); DCHECK(result_container_views_.size() == separators_.size() + 1);
int result_count = 0;
// Only sort and layout the containers when they have all updated. // Only sort and layout the containers when they have all updated.
for (SearchResultContainerView* view : result_container_views_) { for (SearchResultContainerView* view : result_container_views_) {
if (view->UpdateScheduled()) if (view->UpdateScheduled())
return; return;
result_count += view->num_results();
} }
last_search_result_count_ = result_count;
ReorderSearchResultContainers(); ReorderSearchResultContainers();
ScheduleResultsChangedA11yNotification();
if (!app_list_features::IsSearchBoxSelectionEnabled()) { if (!app_list_features::IsSearchBoxSelectionEnabled()) {
views::View* focused_view = GetFocusManager()->GetFocusedView(); views::View* focused_view = GetFocusManager()->GetFocusedView();
...@@ -370,8 +460,6 @@ void SearchResultPageView::OnSearchResultContainerResultsChanged() { ...@@ -370,8 +460,6 @@ void SearchResultPageView::OnSearchResultContainerResultsChanged() {
} }
first_result_view_ = result_container_views_[0]->GetFirstResultView(); first_result_view_ = result_container_views_[0]->GetFirstResultView();
if (!first_result_view_)
return;
if (!app_list_features::IsSearchBoxSelectionEnabled()) { if (!app_list_features::IsSearchBoxSelectionEnabled()) {
views::View* focused_view = GetFocusManager()->GetFocusedView(); views::View* focused_view = GetFocusManager()->GetFocusedView();
...@@ -419,6 +507,16 @@ void SearchResultPageView::OnSearchResultContainerResultFocused( ...@@ -419,6 +507,16 @@ void SearchResultPageView::OnSearchResultContainerResultFocused(
} }
} }
void SearchResultPageView::HintTextChanged() {}
void SearchResultPageView::Update() {
notify_a11y_results_changed_timer_.Stop();
}
void SearchResultPageView::SearchEngineChanged() {}
void SearchResultPageView::ShowAssistantChanged() {}
void SearchResultPageView::OnAssistantPrivacyInfoViewCloseButtonPressed() { void SearchResultPageView::OnAssistantPrivacyInfoViewCloseButtonPressed() {
ReorderSearchResultContainers(); ReorderSearchResultContainers();
} }
...@@ -427,6 +525,7 @@ void SearchResultPageView::OnHidden() { ...@@ -427,6 +525,7 @@ void SearchResultPageView::OnHidden() {
// Hide the search results page when it is behind search box to avoid focus // Hide the search results page when it is behind search box to avoid focus
// being moved onto suggested apps when zero state is enabled. // being moved onto suggested apps when zero state is enabled.
AppListPage::OnHidden(); AppListPage::OnHidden();
notify_a11y_results_changed_timer_.Stop();
SetVisible(false); SetVisible(false);
for (auto* container_view : result_container_views_) { for (auto* container_view : result_container_views_) {
container_view->SetShown(false); container_view->SetShown(false);
...@@ -438,6 +537,7 @@ void SearchResultPageView::OnShown() { ...@@ -438,6 +537,7 @@ void SearchResultPageView::OnShown() {
for (auto* container_view : result_container_views_) { for (auto* container_view : result_container_views_) {
container_view->SetShown(true); container_view->SetShown(true);
} }
ScheduleResultsChangedA11yNotification();
} }
void SearchResultPageView::OnAnimationStarted(AppListState from_state, void SearchResultPageView::OnAnimationStarted(AppListState from_state,
......
...@@ -9,11 +9,14 @@ ...@@ -9,11 +9,14 @@
#include "ash/app_list/app_list_export.h" #include "ash/app_list/app_list_export.h"
#include "ash/app_list/model/app_list_model.h" #include "ash/app_list/model/app_list_model.h"
#include "ash/app_list/model/search/search_box_model.h"
#include "ash/app_list/model/search/search_box_model_observer.h"
#include "ash/app_list/views/app_list_page.h" #include "ash/app_list/views/app_list_page.h"
#include "ash/app_list/views/result_selection_controller.h" #include "ash/app_list/views/result_selection_controller.h"
#include "ash/app_list/views/search_result_container_view.h" #include "ash/app_list/views/search_result_container_view.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
namespace ash { namespace ash {
...@@ -24,13 +27,14 @@ class ViewShadow; ...@@ -24,13 +27,14 @@ class ViewShadow;
// The search results page for the app list. // The search results page for the app list.
class APP_LIST_EXPORT SearchResultPageView class APP_LIST_EXPORT SearchResultPageView
: public AppListPage, : public AppListPage,
public SearchResultContainerView::Delegate { public SearchResultContainerView::Delegate,
public SearchBoxModelObserver {
public: public:
explicit SearchResultPageView(AppListViewDelegate* view_delegate); SearchResultPageView(AppListViewDelegate* view_delegate,
SearchModel* search_model);
~SearchResultPageView() override; ~SearchResultPageView() override;
void AddSearchResultContainerView( void AddSearchResultContainerView(
SearchModel::SearchResults* result_model,
SearchResultContainerView* result_container); SearchResultContainerView* result_container);
const std::vector<SearchResultContainerView*>& result_container_views() { const std::vector<SearchResultContainerView*>& result_container_views() {
...@@ -45,6 +49,7 @@ class APP_LIST_EXPORT SearchResultPageView ...@@ -45,6 +49,7 @@ class APP_LIST_EXPORT SearchResultPageView
const char* GetClassName() const override; const char* GetClassName() const override;
gfx::Size CalculatePreferredSize() const override; gfx::Size CalculatePreferredSize() const override;
void OnBoundsChanged(const gfx::Rect& previous_bounds) override; void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
// AppListPage overrides: // AppListPage overrides:
void OnHidden() override; void OnHidden() override;
...@@ -65,12 +70,18 @@ class APP_LIST_EXPORT SearchResultPageView ...@@ -65,12 +70,18 @@ class APP_LIST_EXPORT SearchResultPageView
views::View* GetFirstFocusableView() override; views::View* GetFirstFocusableView() override;
views::View* GetLastFocusableView() override; views::View* GetLastFocusableView() override;
// Overridden from SearchResultContainerView::Delegate : // Overridden from SearchResultContainerView::Delegate:
void OnSearchResultContainerResultsChanging() override; void OnSearchResultContainerResultsChanging() override;
void OnSearchResultContainerResultsChanged() override; void OnSearchResultContainerResultsChanged() override;
void OnSearchResultContainerResultFocused( void OnSearchResultContainerResultFocused(
SearchResultBaseView* focused_result_view) override; SearchResultBaseView* focused_result_view) override;
// Overridden from SearchBoxModelObserver:
void HintTextChanged() override;
void Update() override;
void SearchEngineChanged() override;
void ShowAssistantChanged() override;
void OnAssistantPrivacyInfoViewCloseButtonPressed(); void OnAssistantPrivacyInfoViewCloseButtonPressed();
views::View* contents_view() { return contents_view_; } views::View* contents_view() { return contents_view_; }
...@@ -92,8 +103,38 @@ class APP_LIST_EXPORT SearchResultPageView ...@@ -92,8 +103,38 @@ class APP_LIST_EXPORT SearchResultPageView
// Ensures that |scroller_| visible rect contains the newly selected result. // Ensures that |scroller_| visible rect contains the newly selected result.
void SelectedResultChanged(); void SelectedResultChanged();
// Sets whether changes in search result containers should be hidden from the
// accessibility framework.
// This is set while search results are being updated to reduce noisy updates
// sent to the accessibility framework while the search result containers are
// being rebuilt.
// The |ignore| value is reset in NotifyA11yResultsChanged(), at which time
// accessibility framework is notified that the view value/selected children
// have changed.
void SetIgnoreResultChangesForA11y(bool ignore);
// Schedules a call to |NotifyA11yResultsChanged|. Called from
// OnSearchResultContainerResultsChanged() when all result containers have
// finished changing. The goal of the delay is to reduce the noise if the set
// of results for a query has not stabilized, or while the user is still
// changing the query.
void ScheduleResultsChangedA11yNotification();
// Notifies the accessibility framework that the set of search results has
// changed.
// Note: This ensures that results changes are not being hidden from a11y
// framework.
void NotifyA11yResultsChanged();
// If required, sends a kSelection a11y notification for the currently
// selected search result view.
void NotifySelectedResultChanged();
AppListViewDelegate* view_delegate_; AppListViewDelegate* view_delegate_;
// The search model for which the results are displayed.
SearchModel* const search_model_;
// The SearchResultContainerViews that compose the search page. All owned by // The SearchResultContainerViews that compose the search page. All owned by
// the views hierarchy. // the views hierarchy.
std::vector<SearchResultContainerView*> result_container_views_; std::vector<SearchResultContainerView*> result_container_views_;
...@@ -110,10 +151,24 @@ class APP_LIST_EXPORT SearchResultPageView ...@@ -110,10 +151,24 @@ class APP_LIST_EXPORT SearchResultPageView
// The first search result's view or nullptr if there's no search result. // The first search result's view or nullptr if there's no search result.
SearchResultBaseView* first_result_view_ = nullptr; SearchResultBaseView* first_result_view_ = nullptr;
// Timer used to delay calls to NotifyA11yResultsChanged().
base::OneShotTimer notify_a11y_results_changed_timer_;
// Whether the changes in search result containers are being hidden from the
// accessibility framework.
bool ignore_result_changes_for_a11y_ = false;
// The last reported number of search results shown within search result
// containers.
int last_search_result_count_ = 0;
views::View* assistant_privacy_info_view_ = nullptr; views::View* assistant_privacy_info_view_ = nullptr;
std::unique_ptr<ash::ViewShadow> view_shadow_; std::unique_ptr<ash::ViewShadow> view_shadow_;
ScopedObserver<SearchBoxModel, SearchBoxModelObserver> search_box_observer_{
this};
DISALLOW_COPY_AND_ASSIGN(SearchResultPageView); DISALLOW_COPY_AND_ASSIGN(SearchResultPageView);
}; };
......
...@@ -281,7 +281,7 @@ TEST_P(SearchResultTileItemListViewTest, Basic) { ...@@ -281,7 +281,7 @@ TEST_P(SearchResultTileItemListViewTest, Basic) {
ui::AXNodeData node_data; ui::AXNodeData node_data;
view()->children()[first_child + i * child_step]->GetAccessibleNodeData( view()->children()[first_child + i * child_step]->GetAccessibleNodeData(
&node_data); &node_data);
EXPECT_EQ(ax::mojom::Role::kButton, node_data.role); EXPECT_EQ(ax::mojom::Role::kListBoxOption, node_data.role);
EXPECT_EQ("InstalledApp " + base::NumberToString(i) + ", " + EXPECT_EQ("InstalledApp " + base::NumberToString(i) + ", " +
l10n_util::GetStringUTF8( l10n_util::GetStringUTF8(
IDS_APP_ACCESSIBILITY_INSTALLED_APP_ANNOUNCEMENT), IDS_APP_ACCESSIBILITY_INSTALLED_APP_ANNOUNCEMENT),
...@@ -297,7 +297,7 @@ TEST_P(SearchResultTileItemListViewTest, Basic) { ...@@ -297,7 +297,7 @@ TEST_P(SearchResultTileItemListViewTest, Basic) {
view() view()
->children()[first_child + (i + kInstalledApps) * child_step] ->children()[first_child + (i + kInstalledApps) * child_step]
->GetAccessibleNodeData(&node_data); ->GetAccessibleNodeData(&node_data);
EXPECT_EQ(ax::mojom::Role::kButton, node_data.role); EXPECT_EQ(ax::mojom::Role::kListBoxOption, node_data.role);
EXPECT_EQ("PlayStoreApp " + base::NumberToString(i) + ", " + EXPECT_EQ("PlayStoreApp " + base::NumberToString(i) + ", " +
l10n_util::GetStringUTF8( l10n_util::GetStringUTF8(
IDS_APP_ACCESSIBILITY_ARC_APP_ANNOUNCEMENT) + IDS_APP_ACCESSIBILITY_ARC_APP_ANNOUNCEMENT) +
...@@ -313,7 +313,7 @@ TEST_P(SearchResultTileItemListViewTest, Basic) { ...@@ -313,7 +313,7 @@ TEST_P(SearchResultTileItemListViewTest, Basic) {
view() view()
->children()[first_child + (i + start_index) * child_step] ->children()[first_child + (i + start_index) * child_step]
->GetAccessibleNodeData(&node_data); ->GetAccessibleNodeData(&node_data);
EXPECT_EQ(ax::mojom::Role::kButton, node_data.role); EXPECT_EQ(ax::mojom::Role::kListBoxOption, node_data.role);
EXPECT_EQ("RecommendedApp " + base::NumberToString(i) + ", Star rating " + EXPECT_EQ("RecommendedApp " + base::NumberToString(i) + ", Star rating " +
base::NumberToString(i + 1) + ".0, App recommendation", base::NumberToString(i + 1) + ".0, App recommendation",
node_data.GetStringAttribute(ax::mojom::StringAttribute::kName)); node_data.GetStringAttribute(ax::mojom::StringAttribute::kName));
...@@ -354,7 +354,7 @@ TEST_P(SearchResultTileItemListViewTest, TestRecommendations) { ...@@ -354,7 +354,7 @@ TEST_P(SearchResultTileItemListViewTest, TestRecommendations) {
ui::AXNodeData node_data; ui::AXNodeData node_data;
view()->children()[first_index + i * child_step]->GetAccessibleNodeData( view()->children()[first_index + i * child_step]->GetAccessibleNodeData(
&node_data); &node_data);
EXPECT_EQ(ax::mojom::Role::kButton, node_data.role); EXPECT_EQ(ax::mojom::Role::kListBoxOption, node_data.role);
EXPECT_EQ("RecommendedApp " + base::NumberToString(i) + ", Star rating " + EXPECT_EQ("RecommendedApp " + base::NumberToString(i) + ", Star rating " +
base::NumberToString(i + 1) + ".0, App recommendation", base::NumberToString(i + 1) + ".0, App recommendation",
node_data.GetStringAttribute(ax::mojom::StringAttribute::kName)); node_data.GetStringAttribute(ax::mojom::StringAttribute::kName));
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include "ui/gfx/image/image_skia_operations.h" #include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/paint_vector_icon.h" #include "ui/gfx/paint_vector_icon.h"
#include "ui/strings/grit/ui_strings.h" #include "ui/strings/grit/ui_strings.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h" #include "ui/views/background.h"
#include "ui/views/controls/image_view.h" #include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h" #include "ui/views/controls/label.h"
...@@ -89,6 +90,8 @@ SearchResultTileItemView::SearchResultTileItemView( ...@@ -89,6 +90,8 @@ SearchResultTileItemView::SearchResultTileItemView(
// a non-null item makes the tile visible. // a non-null item makes the tile visible.
SetVisible(false); SetVisible(false);
GetViewAccessibility().OverrideIsLeaf(true);
// Prevent the icon view from interfering with our mouse events. // Prevent the icon view from interfering with our mouse events.
icon_ = new views::ImageView; icon_ = new views::ImageView;
icon_->set_can_process_events_within_subtree(false); icon_->set_can_process_events_within_subtree(false);
...@@ -269,6 +272,12 @@ void SearchResultTileItemView::ButtonPressed(views::Button* sender, ...@@ -269,6 +272,12 @@ void SearchResultTileItemView::ButtonPressed(views::Button* sender,
void SearchResultTileItemView::GetAccessibleNodeData( void SearchResultTileItemView::GetAccessibleNodeData(
ui::AXNodeData* node_data) { ui::AXNodeData* node_data) {
views::Button::GetAccessibleNodeData(node_data); views::Button::GetAccessibleNodeData(node_data);
// The tile is a list item in the search result page's result list.
node_data->role = ax::mojom::Role::kListBoxOption;
node_data->AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, selected());
node_data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kClick);
// Specify |ax::mojom::StringAttribute::kDescription| with an empty string, so // Specify |ax::mojom::StringAttribute::kDescription| with an empty string, so
// that long truncated names are not read twice. Details of this issue: - The // that long truncated names are not read twice. Details of this issue: - The
// Play Store app's name is shown in a label |title_|. - If the name is too // Play Store app's name is shown in a label |title_|. - If the name is too
...@@ -464,7 +473,6 @@ void SearchResultTileItemView::SetBadgeIcon(const gfx::ImageSkia& badge_icon) { ...@@ -464,7 +473,6 @@ void SearchResultTileItemView::SetBadgeIcon(const gfx::ImageSkia& badge_icon) {
void SearchResultTileItemView::SetTitle(const base::string16& title) { void SearchResultTileItemView::SetTitle(const base::string16& title) {
title_->SetText(title); title_->SetText(title);
title_->NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged, true);
} }
void SearchResultTileItemView::SetRating(float rating) { void SearchResultTileItemView::SetRating(float rating) {
......
...@@ -332,12 +332,12 @@ void SearchResultView::GetAccessibleNodeData(ui::AXNodeData* node_data) { ...@@ -332,12 +332,12 @@ void SearchResultView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
if (!GetVisible()) if (!GetVisible())
return; return;
// This is a work around to deal with the nested button case(append and remove // Mark the result is a list item in the list of search results.
// Also avoids an issue with the nested button case(append and remove
// button are child button of SearchResultView), which is not supported by // button are child button of SearchResultView), which is not supported by
// ChromeVox. see details in crbug.com/924776. // ChromeVox. see details in crbug.com/924776.
// We change the role of the parent view SearchResultView to kGenericContainer node_data->role = ax::mojom::Role::kListBoxOption;
// i.e., not a kButton anymore. node_data->AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, selected());
node_data->role = ax::mojom::Role::kGenericContainer;
node_data->AddState(ax::mojom::State::kFocusable); node_data->AddState(ax::mojom::State::kFocusable);
node_data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kClick); node_data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kClick);
node_data->SetName(GetAccessibleName()); node_data->SetName(GetAccessibleName());
...@@ -444,13 +444,6 @@ void SearchResultView::OnSearchResultActionActivated(size_t index, ...@@ -444,13 +444,6 @@ void SearchResultView::OnSearchResultActionActivated(size_t index,
} }
} }
void SearchResultView::OnSearchResultActionsUnSelected() {
// If the selection has changed to default result action, announce the
// selection change to a11y stack.
if (selected())
NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
}
bool SearchResultView::IsSearchResultHoveredOrSelected() { bool SearchResultView::IsSearchResultHoveredOrSelected() {
return IsMouseHovered() || selected(); return IsMouseHovered() || selected();
} }
......
...@@ -108,7 +108,6 @@ class APP_LIST_EXPORT SearchResultView ...@@ -108,7 +108,6 @@ class APP_LIST_EXPORT SearchResultView
// SearchResultActionsViewDelegate overrides: // SearchResultActionsViewDelegate overrides:
void OnSearchResultActionActivated(size_t index, int event_flags) override; void OnSearchResultActionActivated(size_t index, int event_flags) override;
void OnSearchResultActionsUnSelected() override;
bool IsSearchResultHoveredOrSelected() override; bool IsSearchResultHoveredOrSelected() override;
// Invoked when the context menu closes. // Invoked when the context menu closes.
......
...@@ -879,6 +879,15 @@ need to be translated for each locale.--> ...@@ -879,6 +879,15 @@ need to be translated for each locale.-->
<message name="IDS_APP_LIST_CLEAR_SEARCHBOX" desc="Tooltip for the button that clears all text from the search box in the app list."> <message name="IDS_APP_LIST_CLEAR_SEARCHBOX" desc="Tooltip for the button that clears all text from the search box in the app list.">
Clear searchbox text Clear searchbox text
</message> </message>
<message name="IDS_APP_LIST_SEARCHBOX_RESULTS_ACCESSIBILITY_ANNOUNCEMENT_ZERO_STATE" desc="The accessibility announcement notifying the user that app list search is displaying a set of suggested items - i.e. items shown as search results by default when the user has not entered any search query.">
Displaying suggestions
</message>
<message name="IDS_APP_LIST_SEARCHBOX_RESULTS_ACCESSIBILITY_ANNOUNCEMENT" desc="The accessibility announcement notifying the user about the number of results shown in app list search, and the query for which the results are displayed.">
Displaying <ph name="result_count">$1<ex>10</ex></ph> results for <ph name="query">$2<ex>search query</ex></ph>
</message>
<message name="IDS_APP_LIST_SEARCHBOX_RESULTS_ACCESSIBILITY_ANNOUNCEMENT_SINGLE_RESULT" desc="The accessibility announcement notifying the user about the number of results shown in app list search, and the query for which the results are displayed. Used if search results contain a single item.">
Displaying 1 result for <ph name="query">$1<ex>search query</ex></ph>
</message>
<message name="IDS_SHELF_ALIGNMENT_BOTTOM" desc="Accessibility announcement notifying users that the shelf is now placed at the bottom of the screen"> <message name="IDS_SHELF_ALIGNMENT_BOTTOM" desc="Accessibility announcement notifying users that the shelf is now placed at the bottom of the screen">
Shelf on bottom Shelf on bottom
</message> </message>
......
af2da80cac63e3cd50aec6720544ebf796fbcc43
\ No newline at end of file
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