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
$1<ex>A VR-enabled website</ex>
</ph> - VR presenting to headset
</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 -->
<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() {
for (tab_groups::TabGroupId group : GetOrderedTabGroups()) {
if (ShouldShowGroup(model_, context_index_, 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(
group_index, tab_group->GetDisplayedTitle(),
group_index, displayed_title,
gfx::ImageSkia(std::make_unique<TabGroupIconImageSource>(
tab_group->visual_data()),
gfx::Size(TabGroupIconImageSource::kIconSize,
......
......@@ -34,23 +34,17 @@ void TabGroup::SetVisualData(
controller_->ChangeTabGroupVisuals(id_);
}
base::string16 TabGroup::GetDisplayedTitle() const {
base::string16 title = visual_data_->title();
if (title.empty()) {
// Generate a descriptive placeholder title for the group.
std::vector<int> tabs_in_group = ListTabs();
TabUIHelper* const tab_ui_helper = TabUIHelper::FromWebContents(
controller_->GetWebContentsAt(tabs_in_group.front()));
constexpr size_t kContextMenuTabTitleMaxLength = 30;
base::string16 format_string = l10n_util::GetPluralStringFUTF16(
IDS_TAB_CXMENU_PLACEHOLDER_GROUP_TITLE, tabs_in_group.size() - 1);
base::string16 short_title;
gfx::ElideString(tab_ui_helper->GetTitle(), kContextMenuTabTitleMaxLength,
&short_title);
title =
base::ReplaceStringPlaceholders(format_string, {short_title}, nullptr);
}
return title;
base::string16 TabGroup::GetContentString() const {
std::vector<int> tabs_in_group = ListTabs();
TabUIHelper* const tab_ui_helper = TabUIHelper::FromWebContents(
controller_->GetWebContentsAt(tabs_in_group.front()));
constexpr size_t kContextMenuTabTitleMaxLength = 30;
base::string16 format_string = l10n_util::GetPluralStringFUTF16(
IDS_TAB_CXMENU_PLACEHOLDER_GROUP_TITLE, tabs_in_group.size() - 1);
base::string16 short_title;
gfx::ElideString(tab_ui_helper->GetTitle(), kContextMenuTabTitleMaxLength,
&short_title);
return base::ReplaceStringPlaceholders(format_string, {short_title}, nullptr);
}
void TabGroup::AddTab() {
......
......@@ -36,10 +36,11 @@ class TabGroup {
}
void SetVisualData(const tab_groups::TabGroupVisualData& visual_data);
// Returns the user-visible group title that will be displayed in context
// menus and tooltips. Generates a descriptive placeholder if the user has
// not yet named the group, otherwise uses the group's name.
base::string16 GetDisplayedTitle() const;
// Returns a user-visible string describing the contents of the group, such as
// "Google Search and 3 other tabs". Used for accessibly describing the group,
// as well as for displaying in context menu items and tooltips when the group
// is unnamed.
base::string16 GetContentString() const;
// Updates internal bookkeeping for group contents, and notifies the
// controller that contents changed when a tab is added.
......
......@@ -2046,6 +2046,19 @@ base::string16 BrowserView::GetAccessibleTabLabel(bool include_app_name,
base::string16 title =
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.
if (tabstrip_->IsTabCrashed(index))
return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_CRASHED_FORMAT, title);
......
......@@ -458,6 +458,11 @@ base::string16 BrowserTabStripController::GetGroupTitle(
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(
const tab_groups::TabGroupId& group) const {
return model_->group_model()->GetTabGroup(group)->visual_data()->color();
......
......@@ -84,6 +84,8 @@ class BrowserTabStripController : public TabStripController,
void OnKeyboardFocusedTabChanged(base::Optional<int> index) override;
base::string16 GetGroupTitle(
const tab_groups::TabGroupId& group_id) const override;
base::string16 GetGroupContentString(
const tab_groups::TabGroupId& group_id) const override;
tab_groups::TabGroupColorId GetGroupColorId(
const tab_groups::TabGroupId& group_id) const override;
void SetVisualDataForGroup(
......
......@@ -72,6 +72,11 @@ base::string16 FakeBaseTabStripController::GetGroupTitle(
return fake_group_data_.title();
}
base::string16 FakeBaseTabStripController::GetGroupContentString(
const tab_groups::TabGroupId& group_id) const {
return base::string16();
}
tab_groups::TabGroupColorId FakeBaseTabStripController::GetGroupColorId(
const tab_groups::TabGroupId& group_id) const {
return fake_group_data_.color();
......
......@@ -61,6 +61,8 @@ class FakeBaseTabStripController : public TabStripController {
void OnKeyboardFocusedTabChanged(base::Optional<int> index) override;
base::string16 GetGroupTitle(
const tab_groups::TabGroupId& group_id) const override;
base::string16 GetGroupContentString(
const tab_groups::TabGroupId& group_id) const override;
tab_groups::TabGroupColorId GetGroupColorId(
const tab_groups::TabGroupId& group_id) const override;
void SetVisualDataForGroup(
......
......@@ -19,11 +19,13 @@
#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_types.h"
#include "chrome/grit/generated_resources.h"
#include "components/tab_groups/tab_group_color.h"
#include "components/tab_groups/tab_group_id.h"
#include "components/tab_groups/tab_group_visual_data.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/insets.h"
......@@ -199,15 +201,25 @@ void TabGroupHeader::OnGestureEvent(ui::GestureEvent* event) {
event->SetHandled();
}
void TabGroupHeader::OnFocus() {
View::OnFocus();
tab_strip_->UpdateHoverCard(nullptr);
}
void TabGroupHeader::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kTabList;
node_data->AddState(ax::mojom::State::kEditable);
base::string16 name = tab_strip_->GetGroupTitle(group().value());
if (name.empty()) {
node_data->SetNameExplicitlyEmpty();
base::string16 title =
tab_strip_->controller()->GetGroupTitle(group().value());
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 {
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 {
void OnMouseReleased(const ui::MouseEvent& event) override;
void OnMouseEntered(const ui::MouseEvent& event) override;
void OnGestureEvent(ui::GestureEvent* event) override;
void OnFocus() override;
void OnThemeChanged() override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
TabSlotView::ViewType GetTabSlotViewType() const override;
......
......@@ -141,10 +141,14 @@ class TabStripController {
// to |index|.
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(
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|.
virtual tab_groups::TabGroupColorId GetGroupColorId(
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