Commit b5bee6f9 authored by Connie Wan's avatar Connie Wan Committed by Commit Bot

Add screen reader support for groups

- Tabs will read out their group membership.
- Group headers will read out their tab contents.

Tiny side bonus: Focusing on group headers will no longer show the wrong hovercard.

Bug: 1045844
Change-Id: I5fc98231a5c5a0372aa75e31a5c3a6e321ce24b0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2025698
Commit-Queue: Connie Wan <connily@chromium.org>
Reviewed-by: default avatarCharlene Yan <cyan@chromium.org>
Reviewed-by: default avatarPeter Kasting <pkasting@chromium.org>
Cr-Commit-Position: refs/heads/master@{#736582}
parent 74ecb181
...@@ -9573,6 +9573,18 @@ Please help our engineers fix this problem. Tell us what happened right before y ...@@ -9573,6 +9573,18 @@ Please help our engineers fix this problem. Tell us what happened right before y
$1<ex>A VR-enabled website</ex> $1<ex>A VR-enabled website</ex>
</ph> - VR presenting to headset </ph> - VR presenting to headset
</message> </message>
<message name="IDS_TAB_AX_LABEL_UNNAMED_GROUP_FORMAT" desc="Accessibility label text for a tab, when it is in a group that has not been named by the user. Example: 'Google Search - Part of unnamed group'">
<ph name="WINDOW_TITLE">$1<ex>Google Search</ex></ph> - Part of unnamed group
</message>
<message name="IDS_TAB_AX_LABEL_NAMED_GROUP_FORMAT" desc="Accessibility label text for a tab, when it is in a group that has been named by the user. Example: 'Google Search - Part of group MyGroupName'">
<ph name="WINDOW_TITLE">$1<ex>Google Search</ex></ph> - Part of group <ph name="GROUP_NAME">$2</ph>
</message>
<message name="IDS_GROUP_AX_LABEL_UNNAMED_GROUP_FORMAT" desc="Accessibility label text for a tab group header, when it has not been named by the user. Example: 'Unnamed group - Google Search and 3 other tabs'">
Unnamed group - <ph name="GROUP_CONTENTS">$1<ex>Google Search and 3 other tabs</ex></ph>
</message>
<message name="IDS_GROUP_AX_LABEL_NAMED_GROUP_FORMAT" desc="Accessibility label text for a tab group header, when it has been named by the user. Example: 'Group MyGroupName - Google Search and 3 other tabs'">
Group <ph name="GROUP_NAME">$1</ph> - <ph name="GROUP_CONTENTS">$2<ex>Google Search and 3 other tabs</ex></ph>
</message>
<!-- ProcessSingleton --> <!-- ProcessSingleton -->
<message name="IDS_PROFILE_IN_USE_LINUX_QUIT" desc="Text of button in profile in use dialog to quit without doing anything."> <message name="IDS_PROFILE_IN_USE_LINUX_QUIT" desc="Text of button in profile in use dialog to quit without doing anything.">
......
...@@ -88,8 +88,11 @@ void ExistingTabGroupSubMenuModel::Build() { ...@@ -88,8 +88,11 @@ void ExistingTabGroupSubMenuModel::Build() {
for (tab_groups::TabGroupId group : GetOrderedTabGroups()) { for (tab_groups::TabGroupId group : GetOrderedTabGroups()) {
if (ShouldShowGroup(model_, context_index_, group)) { if (ShouldShowGroup(model_, context_index_, group)) {
const TabGroup* tab_group = model_->group_model()->GetTabGroup(group); const TabGroup* tab_group = model_->group_model()->GetTabGroup(group);
const base::string16 group_title = tab_group->visual_data()->title();
const base::string16 displayed_title =
group_title.empty() ? tab_group->GetContentString() : group_title;
AddItemWithIcon( AddItemWithIcon(
group_index, tab_group->GetDisplayedTitle(), group_index, displayed_title,
gfx::ImageSkia(std::make_unique<TabGroupIconImageSource>( gfx::ImageSkia(std::make_unique<TabGroupIconImageSource>(
tab_group->visual_data()), tab_group->visual_data()),
gfx::Size(TabGroupIconImageSource::kIconSize, gfx::Size(TabGroupIconImageSource::kIconSize,
......
...@@ -34,23 +34,17 @@ void TabGroup::SetVisualData( ...@@ -34,23 +34,17 @@ void TabGroup::SetVisualData(
controller_->ChangeTabGroupVisuals(id_); controller_->ChangeTabGroupVisuals(id_);
} }
base::string16 TabGroup::GetDisplayedTitle() const { base::string16 TabGroup::GetContentString() const {
base::string16 title = visual_data_->title(); std::vector<int> tabs_in_group = ListTabs();
if (title.empty()) { TabUIHelper* const tab_ui_helper = TabUIHelper::FromWebContents(
// Generate a descriptive placeholder title for the group. controller_->GetWebContentsAt(tabs_in_group.front()));
std::vector<int> tabs_in_group = ListTabs(); constexpr size_t kContextMenuTabTitleMaxLength = 30;
TabUIHelper* const tab_ui_helper = TabUIHelper::FromWebContents( base::string16 format_string = l10n_util::GetPluralStringFUTF16(
controller_->GetWebContentsAt(tabs_in_group.front())); IDS_TAB_CXMENU_PLACEHOLDER_GROUP_TITLE, tabs_in_group.size() - 1);
constexpr size_t kContextMenuTabTitleMaxLength = 30; base::string16 short_title;
base::string16 format_string = l10n_util::GetPluralStringFUTF16( gfx::ElideString(tab_ui_helper->GetTitle(), kContextMenuTabTitleMaxLength,
IDS_TAB_CXMENU_PLACEHOLDER_GROUP_TITLE, tabs_in_group.size() - 1); &short_title);
base::string16 short_title; return base::ReplaceStringPlaceholders(format_string, {short_title}, nullptr);
gfx::ElideString(tab_ui_helper->GetTitle(), kContextMenuTabTitleMaxLength,
&short_title);
title =
base::ReplaceStringPlaceholders(format_string, {short_title}, nullptr);
}
return title;
} }
void TabGroup::AddTab() { void TabGroup::AddTab() {
......
...@@ -36,10 +36,11 @@ class TabGroup { ...@@ -36,10 +36,11 @@ class TabGroup {
} }
void SetVisualData(const tab_groups::TabGroupVisualData& visual_data); void SetVisualData(const tab_groups::TabGroupVisualData& visual_data);
// Returns the user-visible group title that will be displayed in context // Returns a user-visible string describing the contents of the group, such as
// menus and tooltips. Generates a descriptive placeholder if the user has // "Google Search and 3 other tabs". Used for accessibly describing the group,
// not yet named the group, otherwise uses the group's name. // as well as for displaying in context menu items and tooltips when the group
base::string16 GetDisplayedTitle() const; // is unnamed.
base::string16 GetContentString() const;
// Updates internal bookkeeping for group contents, and notifies the // Updates internal bookkeeping for group contents, and notifies the
// controller that contents changed when a tab is added. // controller that contents changed when a tab is added.
......
...@@ -2046,6 +2046,19 @@ base::string16 BrowserView::GetAccessibleTabLabel(bool include_app_name, ...@@ -2046,6 +2046,19 @@ base::string16 BrowserView::GetAccessibleTabLabel(bool include_app_name,
base::string16 title = base::string16 title =
browser_->GetWindowTitleForTab(include_app_name, index); browser_->GetWindowTitleForTab(include_app_name, index);
base::Optional<tab_groups::TabGroupId> group =
tabstrip_->tab_at(index)->group();
if (group.has_value()) {
base::string16 group_title = tabstrip_->GetGroupTitle(group.value());
if (group_title.empty()) {
title = l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_UNNAMED_GROUP_FORMAT,
title);
} else {
title = l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_NAMED_GROUP_FORMAT,
title, group_title);
}
}
// Tab has crashed. // Tab has crashed.
if (tabstrip_->IsTabCrashed(index)) if (tabstrip_->IsTabCrashed(index))
return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_CRASHED_FORMAT, title); return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_CRASHED_FORMAT, title);
......
...@@ -458,6 +458,11 @@ base::string16 BrowserTabStripController::GetGroupTitle( ...@@ -458,6 +458,11 @@ base::string16 BrowserTabStripController::GetGroupTitle(
return model_->group_model()->GetTabGroup(group)->visual_data()->title(); return model_->group_model()->GetTabGroup(group)->visual_data()->title();
} }
base::string16 BrowserTabStripController::GetGroupContentString(
const tab_groups::TabGroupId& group) const {
return model_->group_model()->GetTabGroup(group)->GetContentString();
}
tab_groups::TabGroupColorId BrowserTabStripController::GetGroupColorId( tab_groups::TabGroupColorId BrowserTabStripController::GetGroupColorId(
const tab_groups::TabGroupId& group) const { const tab_groups::TabGroupId& group) const {
return model_->group_model()->GetTabGroup(group)->visual_data()->color(); return model_->group_model()->GetTabGroup(group)->visual_data()->color();
......
...@@ -84,6 +84,8 @@ class BrowserTabStripController : public TabStripController, ...@@ -84,6 +84,8 @@ class BrowserTabStripController : public TabStripController,
void OnKeyboardFocusedTabChanged(base::Optional<int> index) override; void OnKeyboardFocusedTabChanged(base::Optional<int> index) override;
base::string16 GetGroupTitle( base::string16 GetGroupTitle(
const tab_groups::TabGroupId& group_id) const override; const tab_groups::TabGroupId& group_id) const override;
base::string16 GetGroupContentString(
const tab_groups::TabGroupId& group_id) const override;
tab_groups::TabGroupColorId GetGroupColorId( tab_groups::TabGroupColorId GetGroupColorId(
const tab_groups::TabGroupId& group_id) const override; const tab_groups::TabGroupId& group_id) const override;
void SetVisualDataForGroup( void SetVisualDataForGroup(
......
...@@ -72,6 +72,11 @@ base::string16 FakeBaseTabStripController::GetGroupTitle( ...@@ -72,6 +72,11 @@ base::string16 FakeBaseTabStripController::GetGroupTitle(
return fake_group_data_.title(); return fake_group_data_.title();
} }
base::string16 FakeBaseTabStripController::GetGroupContentString(
const tab_groups::TabGroupId& group_id) const {
return base::string16();
}
tab_groups::TabGroupColorId FakeBaseTabStripController::GetGroupColorId( tab_groups::TabGroupColorId FakeBaseTabStripController::GetGroupColorId(
const tab_groups::TabGroupId& group_id) const { const tab_groups::TabGroupId& group_id) const {
return fake_group_data_.color(); return fake_group_data_.color();
......
...@@ -61,6 +61,8 @@ class FakeBaseTabStripController : public TabStripController { ...@@ -61,6 +61,8 @@ class FakeBaseTabStripController : public TabStripController {
void OnKeyboardFocusedTabChanged(base::Optional<int> index) override; void OnKeyboardFocusedTabChanged(base::Optional<int> index) override;
base::string16 GetGroupTitle( base::string16 GetGroupTitle(
const tab_groups::TabGroupId& group_id) const override; const tab_groups::TabGroupId& group_id) const override;
base::string16 GetGroupContentString(
const tab_groups::TabGroupId& group_id) const override;
tab_groups::TabGroupColorId GetGroupColorId( tab_groups::TabGroupColorId GetGroupColorId(
const tab_groups::TabGroupId& group_id) const override; const tab_groups::TabGroupId& group_id) const override;
void SetVisualDataForGroup( void SetVisualDataForGroup(
......
...@@ -19,11 +19,13 @@ ...@@ -19,11 +19,13 @@
#include "chrome/browser/ui/views/tabs/tab_strip_controller.h" #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
#include "chrome/browser/ui/views/tabs/tab_strip_layout.h" #include "chrome/browser/ui/views/tabs/tab_strip_layout.h"
#include "chrome/browser/ui/views/tabs/tab_strip_types.h" #include "chrome/browser/ui/views/tabs/tab_strip_types.h"
#include "chrome/grit/generated_resources.h"
#include "components/tab_groups/tab_group_color.h" #include "components/tab_groups/tab_group_color.h"
#include "components/tab_groups/tab_group_id.h" #include "components/tab_groups/tab_group_id.h"
#include "components/tab_groups/tab_group_visual_data.h" #include "components/tab_groups/tab_group_visual_data.h"
#include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkPath.h" #include "third_party/skia/include/core/SkPath.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/canvas.h" #include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h" #include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/insets.h"
...@@ -199,15 +201,25 @@ void TabGroupHeader::OnGestureEvent(ui::GestureEvent* event) { ...@@ -199,15 +201,25 @@ void TabGroupHeader::OnGestureEvent(ui::GestureEvent* event) {
event->SetHandled(); event->SetHandled();
} }
void TabGroupHeader::OnFocus() {
View::OnFocus();
tab_strip_->UpdateHoverCard(nullptr);
}
void TabGroupHeader::GetAccessibleNodeData(ui::AXNodeData* node_data) { void TabGroupHeader::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kTabList; node_data->role = ax::mojom::Role::kTabList;
node_data->AddState(ax::mojom::State::kEditable); node_data->AddState(ax::mojom::State::kEditable);
base::string16 name = tab_strip_->GetGroupTitle(group().value()); base::string16 title =
if (name.empty()) { tab_strip_->controller()->GetGroupTitle(group().value());
node_data->SetNameExplicitlyEmpty(); base::string16 contents =
tab_strip_->controller()->GetGroupContentString(group().value());
if (title.empty()) {
node_data->SetName(l10n_util::GetStringFUTF16(
IDS_GROUP_AX_LABEL_UNNAMED_GROUP_FORMAT, contents));
} else { } else {
node_data->SetName(name); node_data->SetName(l10n_util::GetStringFUTF16(
IDS_GROUP_AX_LABEL_NAMED_GROUP_FORMAT, title, contents));
} }
} }
......
...@@ -33,6 +33,7 @@ class TabGroupHeader : public TabSlotView { ...@@ -33,6 +33,7 @@ class TabGroupHeader : public TabSlotView {
void OnMouseReleased(const ui::MouseEvent& event) override; void OnMouseReleased(const ui::MouseEvent& event) override;
void OnMouseEntered(const ui::MouseEvent& event) override; void OnMouseEntered(const ui::MouseEvent& event) override;
void OnGestureEvent(ui::GestureEvent* event) override; void OnGestureEvent(ui::GestureEvent* event) override;
void OnFocus() override;
void OnThemeChanged() override; void OnThemeChanged() override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override; void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
TabSlotView::ViewType GetTabSlotViewType() const override; TabSlotView::ViewType GetTabSlotViewType() const override;
......
...@@ -141,10 +141,14 @@ class TabStripController { ...@@ -141,10 +141,14 @@ class TabStripController {
// to |index|. // to |index|.
virtual void OnKeyboardFocusedTabChanged(base::Optional<int> index) = 0; virtual void OnKeyboardFocusedTabChanged(base::Optional<int> index) = 0;
// Returns the displayed title of the given |group|. // Returns the title of the given |group|.
virtual base::string16 GetGroupTitle( virtual base::string16 GetGroupTitle(
const tab_groups::TabGroupId& group) const = 0; const tab_groups::TabGroupId& group) const = 0;
// Returns the string describing the contents of the given |group|.
virtual base::string16 GetGroupContentString(
const tab_groups::TabGroupId& group) const = 0;
// Returns the color ID of the given |group|. // Returns the color ID of the given |group|.
virtual tab_groups::TabGroupColorId GetGroupColorId( virtual tab_groups::TabGroupColorId GetGroupColorId(
const tab_groups::TabGroupId& group) const = 0; const tab_groups::TabGroupId& group) const = 0;
......
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