Commit dfa5f181 authored by Abigail Klein's avatar Abigail Klein Committed by Chromium LUCI CQ

[Live Caption] Refactor caption bubble accessibility.

Rather than override the caption bubble's view accessibility with
virtual children, override the label's view accessibility with a
different AXNode per line. This allows us to take advantage of the
views' visibility and bounds--whereas previously we needed to keep track
of whether the error was visible or not.

This CL also renames functions and variables from virtual_children to
ax_lines. It sets the label role to be kParagraph and each ax_line role
to be kStaticText.

Bug: 1055150
Change-Id: Ic70187b1e0159d4fa012bc44e1ec40b2357717e8
AX-Relnotes: N/A (Feature has not launched)
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2602660Reviewed-by: default avatarJosiah Krutz <josiahk@google.com>
Commit-Queue: Abigail Klein <abigailbklein@google.com>
Cr-Commit-Position: refs/heads/master@{#839778}
parent b5bcbdf8
......@@ -193,6 +193,85 @@ class CaptionBubbleFrameView : public views::BubbleFrameView {
BEGIN_METADATA(CaptionBubbleFrameView, views::BubbleFrameView)
END_METADATA
class CaptionBubbleLabel : public views::Label {
public:
METADATA_HEADER(CaptionBubbleLabel);
CaptionBubbleLabel() = default;
~CaptionBubbleLabel() override = default;
CaptionBubbleLabel(const CaptionBubbleLabel&) = delete;
CaptionBubbleLabel& operator=(const CaptionBubbleLabel&) = delete;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
node_data->role = ax::mojom::Role::kParagraph;
}
void SetText(const base::string16& text) override {
views::Label::SetText(text);
// Only update ViewAccessibility if accessibility is enabled.
if (content::BrowserAccessibilityState::GetInstance()
->GetAccessibilityMode()
.is_mode_off()) {
return;
}
auto& ax_lines = GetViewAccessibility().virtual_children();
if (text.empty() && !ax_lines.empty()) {
GetViewAccessibility().RemoveAllVirtualChildViews();
return;
}
const size_t num_lines = GetRequiredLines();
size_t start = 0;
for (size_t i = 0; i < num_lines - 1; ++i) {
size_t end = GetTextIndexOfLine(i + 1);
base::string16 substring = text.substr(start, end - start);
UpdateAXLine(substring, i, gfx::Range(start, end));
start = end;
}
base::string16 substring = text.substr(start, text.size() - start);
if (!substring.empty()) {
UpdateAXLine(substring, num_lines - 1, gfx::Range(start, text.size()));
}
// Remove all ax_lines that don't have a corresponding line.
size_t num_ax_lines = ax_lines.size();
for (size_t i = num_lines; i < num_ax_lines; ++i) {
GetViewAccessibility().RemoveVirtualChildView(ax_lines.back().get());
}
NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged, true);
}
private:
void UpdateAXLine(const base::string16& line_text,
const size_t line_index,
const gfx::Range& text_range) {
auto& ax_lines = GetViewAccessibility().virtual_children();
// Add a new virtual child for a new line of text.
DCHECK(line_index <= ax_lines.size());
if (line_index == ax_lines.size()) {
auto ax_line = std::make_unique<views::AXVirtualView>();
ax_line->GetCustomData().role = ax::mojom::Role::kStaticText;
GetViewAccessibility().AddVirtualChildView(std::move(ax_line));
}
// Set the virtual child's name as line text.
ui::AXNodeData& ax_node_data = ax_lines[line_index]->GetCustomData();
if (base::UTF8ToUTF16(ax_node_data.GetStringAttribute(
ax::mojom::StringAttribute::kName)) != line_text) {
ax_node_data.SetName(line_text);
std::vector<gfx::Rect> bounds = GetSubstringBounds(text_range);
DCHECK_EQ(bounds.size(), 1u);
ax_node_data.relative_bounds.bounds = gfx::RectF(bounds[0]);
}
}
};
BEGIN_METADATA(CaptionBubbleLabel, views::Label)
END_METADATA
CaptionBubble::CaptionBubble(views::View* anchor,
BrowserView* browser_view,
base::OnceClosure destroyed_callback)
......@@ -345,7 +424,7 @@ void CaptionBubble::Init() {
// The caption bubble starts out hidden and unable to be activated.
SetCanActivate(false);
auto label = std::make_unique<views::Label>();
auto label = std::make_unique<CaptionBubbleLabel>();
label->SetMultiLine(true);
label->SetMaximumWidth(kMaxWidthDip - kSidePaddingDip * 2);
label->SetEnabledColor(SK_ColorWHITE);
......@@ -366,6 +445,7 @@ void CaptionBubble::Init() {
title->SetBackgroundColor(SK_ColorTRANSPARENT);
title->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
title->SetText(l10n_util::GetStringUTF16(IDS_LIVE_CAPTION_BUBBLE_TITLE));
title->GetViewAccessibility().OverrideIsIgnored(true);
auto error_text = std::make_unique<views::Label>();
error_text->SetEnabledColor(SK_ColorWHITE);
......@@ -510,11 +590,8 @@ void CaptionBubble::OnBlur() {
}
void CaptionBubble::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kDialog;
node_data->SetName(title_->GetText());
node_data->role = ax::mojom::Role::kCaption;
if (model_ && model_->HasError()) {
node_data->SetDescription(error_text_->GetText());
}
}
void CaptionBubble::AddedToWidget() {
......@@ -564,64 +641,6 @@ void CaptionBubble::OnTextChanged() {
UpdateBubbleAndTitleVisibility();
if (GetWidget()->IsVisible())
inactivity_timer_->Reset();
// Only update ViewAccessibility if accessibility is enabled.
if (content::BrowserAccessibilityState::GetInstance()
->GetAccessibilityMode()
.is_mode_off() ||
model_->HasError()) {
return;
}
auto& virtual_children = GetViewAccessibility().virtual_children();
if (text.empty() && !virtual_children.empty()) {
GetViewAccessibility().RemoveAllVirtualChildViews();
return;
}
const size_t num_lines = GetNumLinesInLabel();
size_t start = 0;
for (size_t i = 0; i < num_lines - 1; ++i) {
size_t end = GetTextIndexOfLineInLabel(i + 1);
std::string substring = text.substr(start, end - start);
AddVirtualChildView(substring, i, gfx::Range(start, end));
start = end;
}
std::string substring = text.substr(start, text.size() - start);
if (!substring.empty()) {
AddVirtualChildView(substring, num_lines - 1,
gfx::Range(start, text.size()));
}
// Remove all virtual children that don't have a corresponding line.
size_t num_virtual_children = virtual_children.size();
for (size_t i = num_lines; i < num_virtual_children; ++i) {
GetViewAccessibility().RemoveVirtualChildView(
virtual_children.back().get());
}
}
void CaptionBubble::AddVirtualChildView(const std::string& name,
const size_t line_index,
const gfx::Range& range) {
auto& virtual_children = GetViewAccessibility().virtual_children();
// Add a new virtual child for a new line of text.
DCHECK(line_index <= virtual_children.size());
if (line_index == virtual_children.size()) {
auto view = std::make_unique<views::AXVirtualView>();
GetViewAccessibility().AddVirtualChildView(std::move(view));
}
// Set the virtual child's name as the content of the line.
ui::AXNodeData& ax_node_data = virtual_children[line_index]->GetCustomData();
if (ax_node_data.GetStringAttribute(ax::mojom::StringAttribute::kName) !=
name) {
ax_node_data.SetName(name);
std::vector<gfx::Rect> bounds = label_->GetSubstringBounds(range);
DCHECK_EQ(bounds.size(), 1u);
ax_node_data.relative_bounds.bounds = gfx::RectF(bounds[0]);
}
}
void CaptionBubble::OnErrorChanged() {
......@@ -632,14 +651,6 @@ void CaptionBubble::OnErrorChanged() {
// The error is only 1 line, so redraw the bubble.
Redraw();
if (has_error &&
!content::BrowserAccessibilityState::GetInstance()
->GetAccessibilityMode()
.is_mode_off() &&
!GetViewAccessibility().virtual_children().empty()) {
GetViewAccessibility().RemoveAllVirtualChildViews();
}
}
void CaptionBubble::OnIsExpandedChanged() {
......@@ -776,18 +787,18 @@ void CaptionBubble::OnInactivityTimeout() {
GetWidget()->Hide();
}
std::string CaptionBubble::GetLabelTextForTesting() {
return base::UTF16ToUTF8(label_->GetText());
views::Label* CaptionBubble::GetLabelForTesting() {
return static_cast<views::Label*>(label_);
}
std::vector<std::string> CaptionBubble::GetVirtualChildrenTextForTesting() {
auto& virtual_children = GetViewAccessibility().virtual_children();
std::vector<std::string> texts;
for (auto& virtual_child : virtual_children) {
texts.push_back(virtual_child->GetCustomData().GetStringAttribute(
std::vector<std::string> CaptionBubble::GetAXLineTextForTesting() {
auto& ax_lines = label_->GetViewAccessibility().virtual_children();
std::vector<std::string> line_texts;
for (auto& ax_line : ax_lines) {
line_texts.push_back(ax_line->GetCustomData().GetStringAttribute(
ax::mojom::StringAttribute::kName));
}
return texts;
return line_texts;
}
base::RetainingOneShotTimer* CaptionBubble::GetInactivityTimerForTesting() {
......
......@@ -38,6 +38,7 @@ class BrowserView;
namespace captions {
class CaptionBubbleFrameView;
class CaptionBubbleLabel;
///////////////////////////////////////////////////////////////////////////////
// Caption Bubble
......@@ -76,7 +77,7 @@ class CaptionBubble : public views::BubbleDialogDelegateView {
// Returns the number of lines in the caption bubble label that are rendered.
size_t GetNumLinesInLabel() const;
std::string GetLabelTextForTesting();
views::Label* GetLabelForTesting();
base::RetainingOneShotTimer* GetInactivityTimerForTesting();
void set_tick_clock_for_testing(const base::TickClock* tick_clock) {
tick_clock_ = tick_clock;
......@@ -133,10 +134,7 @@ class CaptionBubble : public views::BubbleDialogDelegateView {
views::Button::PressedCallback callback,
const gfx::VectorIcon& icon,
const int tooltip_text_id);
void AddVirtualChildView(const std::string& name,
const size_t i,
const gfx::Range& range);
std::vector<std::string> GetVirtualChildrenTextForTesting();
std::vector<std::string> GetAXLineTextForTesting();
// After 5 seconds of inactivity, hide the caption bubble. Activity is defined
// as transcription received from the speech service or user interacting with
......@@ -144,7 +142,7 @@ class CaptionBubble : public views::BubbleDialogDelegateView {
void OnInactivityTimeout();
// Unowned. Owned by views hierarchy.
views::Label* label_;
CaptionBubbleLabel* label_;
views::Label* title_;
views::Label* error_text_;
views::ImageView* error_icon_;
......
......@@ -140,7 +140,10 @@ bool CaptionBubbleControllerViews::IsWidgetVisibleForTesting() {
}
std::string CaptionBubbleControllerViews::GetBubbleLabelTextForTesting() {
return caption_bubble_ ? caption_bubble_->GetLabelTextForTesting() : "";
return caption_bubble_
? base::UTF16ToUTF8(
caption_bubble_->GetLabelForTesting()->GetText()) // IN-TEST
: "";
}
} // namespace captions
......@@ -60,7 +60,8 @@ class CaptionBubbleControllerViewsTest : public InProcessBrowserTest {
}
views::Label* GetLabel() {
return controller_ ? controller_->caption_bubble_->label_ : nullptr;
return controller_ ? controller_->caption_bubble_->GetLabelForTesting()
: nullptr;
}
views::Label* GetTitle() {
......@@ -162,8 +163,8 @@ class CaptionBubbleControllerViewsTest : public InProcessBrowserTest {
browser()->tab_strip_model()->GetWebContentsAt(tab_index));
}
std::vector<std::string> GetVirtualChildrenText() {
return GetBubble()->GetVirtualChildrenTextForTesting();
std::vector<std::string> GetAXLineText() {
return GetBubble()->GetAXLineTextForTesting();
}
void SetTickClockForTesting(const base::TickClock* tick_clock) {
......@@ -903,20 +904,20 @@ IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest,
// If accessibility is disabled, virtual children aren't computed.
content::BrowserAccessibilityState::GetInstance()->DisableAccessibility();
OnFinalTranscription("A dog's nose print");
EXPECT_EQ(0u, GetVirtualChildrenText().size());
EXPECT_EQ(0u, GetAXLineText().size());
// When accessibility is enabled, virtual children are computed.
content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
OnFinalTranscription("is unique");
EXPECT_EQ(1u, GetVirtualChildrenText().size());
EXPECT_EQ("A dog's nose print is unique", GetVirtualChildrenText()[0]);
EXPECT_EQ(1u, GetAXLineText().size());
EXPECT_EQ("A dog's nose print is unique", GetAXLineText()[0]);
// When accessibility is disabled, virtual children are no longer being
// updated.
content::BrowserAccessibilityState::GetInstance()->DisableAccessibility();
OnFinalTranscription("like a fingerprint");
EXPECT_EQ(1u, GetVirtualChildrenText().size());
EXPECT_EQ("A dog's nose print is unique", GetVirtualChildrenText()[0]);
EXPECT_EQ(1u, GetAXLineText().size());
EXPECT_EQ("A dog's nose print is unique", GetAXLineText()[0]);
}
IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest,
......@@ -927,79 +928,55 @@ IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest,
content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
OnPartialTranscription(line);
EXPECT_EQ(1u, GetVirtualChildrenText().size());
EXPECT_EQ(line, GetVirtualChildrenText()[0]);
EXPECT_EQ(1u, GetAXLineText().size());
EXPECT_EQ(line, GetAXLineText()[0]);
OnPartialTranscription(line + line);
EXPECT_EQ(2u, GetVirtualChildrenText().size());
EXPECT_EQ(line, GetVirtualChildrenText()[0]);
EXPECT_EQ(line, GetVirtualChildrenText()[1]);
EXPECT_EQ(2u, GetAXLineText().size());
EXPECT_EQ(line, GetAXLineText()[0]);
EXPECT_EQ(line, GetAXLineText()[1]);
OnPartialTranscription(line);
EXPECT_EQ(1u, GetVirtualChildrenText().size());
EXPECT_EQ(line, GetVirtualChildrenText()[0]);
EXPECT_EQ(1u, GetAXLineText().size());
EXPECT_EQ(line, GetAXLineText()[0]);
}
IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest,
AccessibleTextClearsWhenBubbleCloses) {
content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
OnFinalTranscription("Dogs' noses are wet to help them smell.");
EXPECT_EQ(1u, GetVirtualChildrenText().size());
EXPECT_EQ("Dogs' noses are wet to help them smell.",
GetVirtualChildrenText()[0]);
EXPECT_EQ(1u, GetAXLineText().size());
EXPECT_EQ("Dogs' noses are wet to help them smell.", GetAXLineText()[0]);
ClickButton(GetCloseButton());
EXPECT_EQ(0u, GetVirtualChildrenText().size());
EXPECT_EQ(0u, GetAXLineText().size());
}
IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest,
AccessibleTextClearsWhenTabRefreshes) {
content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
OnFinalTranscription("Newfoundlands are amazing lifeguards.");
EXPECT_EQ(1u, GetVirtualChildrenText().size());
EXPECT_EQ("Newfoundlands are amazing lifeguards.",
GetVirtualChildrenText()[0]);
EXPECT_EQ(1u, GetAXLineText().size());
EXPECT_EQ("Newfoundlands are amazing lifeguards.", GetAXLineText()[0]);
chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
content::WaitForLoadStop(
browser()->tab_strip_model()->GetActiveWebContents());
EXPECT_EQ(0u, GetVirtualChildrenText().size());
EXPECT_EQ(0u, GetAXLineText().size());
}
IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest,
AccessibleTextChangesWhenTabChanges) {
content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
OnFinalTranscription("3 dogs survived the Titanic sinking.");
EXPECT_EQ(1u, GetVirtualChildrenText().size());
EXPECT_EQ("3 dogs survived the Titanic sinking.",
GetVirtualChildrenText()[0]);
EXPECT_EQ(1u, GetAXLineText().size());
EXPECT_EQ("3 dogs survived the Titanic sinking.", GetAXLineText()[0]);
InsertNewTab();
ActivateTabAt(1);
OnFinalTranscription("30% of Dalmations are deaf in one ear.", 1);
EXPECT_EQ(1u, GetVirtualChildrenText().size());
EXPECT_EQ("30% of Dalmations are deaf in one ear.",
GetVirtualChildrenText()[0]);
EXPECT_EQ(1u, GetAXLineText().size());
EXPECT_EQ("30% of Dalmations are deaf in one ear.", GetAXLineText()[0]);
ActivateTabAt(0);
EXPECT_EQ(1u, GetVirtualChildrenText().size());
EXPECT_EQ("3 dogs survived the Titanic sinking. ",
GetVirtualChildrenText()[0]);
}
IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest,
AccessibleTextClearsOnError) {
content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
OnFinalTranscription("The Saluki is the oldest dog breed.");
EXPECT_EQ(1u, GetVirtualChildrenText().size());
EXPECT_EQ("The Saluki is the oldest dog breed.", GetVirtualChildrenText()[0]);
OnError();
EXPECT_EQ(0u, GetVirtualChildrenText().size());
// Clear the error by refreshing.
chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
content::WaitForLoadStop(
browser()->tab_strip_model()->GetActiveWebContents());
OnFinalTranscription("Chow Chows have black tongues.");
EXPECT_EQ(1u, GetVirtualChildrenText().size());
EXPECT_EQ("Chow Chows have black tongues.", GetVirtualChildrenText()[0]);
EXPECT_EQ(1u, GetAXLineText().size());
EXPECT_EQ("3 dogs survived the Titanic sinking. ", GetAXLineText()[0]);
}
IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest,
......@@ -1012,28 +989,24 @@ IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest,
}
content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
OnFinalTranscription(text);
EXPECT_EQ(9u, GetVirtualChildrenText().size());
EXPECT_EQ(9u, GetAXLineText().size());
for (int i = 0; i < 9; i++) {
EXPECT_EQ(base::NumberToString(i + 31) + line + " ",
GetVirtualChildrenText()[i]);
EXPECT_EQ(base::NumberToString(i + 31) + line + " ", GetAXLineText()[i]);
}
OnPartialTranscription(text);
EXPECT_EQ(39u, GetVirtualChildrenText().size());
EXPECT_EQ(39u, GetAXLineText().size());
for (int i = 0; i < 9; i++) {
EXPECT_EQ(base::NumberToString(i + 31) + line + " ",
GetVirtualChildrenText()[i]);
EXPECT_EQ(base::NumberToString(i + 31) + line + " ", GetAXLineText()[i]);
}
for (int i = 10; i < 40; i++) {
EXPECT_EQ(base::NumberToString(i) + line + " ",
GetVirtualChildrenText()[i - 1]);
EXPECT_EQ(base::NumberToString(i) + line + " ", GetAXLineText()[i - 1]);
}
OnFinalTranscription("a ");
EXPECT_EQ(9u, GetVirtualChildrenText().size());
EXPECT_EQ(9u, GetAXLineText().size());
for (int i = 0; i < 8; i++) {
EXPECT_EQ(base::NumberToString(i + 32) + line + " ",
GetVirtualChildrenText()[i]);
EXPECT_EQ(base::NumberToString(i + 32) + line + " ", GetAXLineText()[i]);
}
EXPECT_EQ("a ", GetVirtualChildrenText()[8]);
EXPECT_EQ("a ", GetAXLineText()[8]);
}
#if !defined(OS_MAC)
......
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