Commit 9b735e2a authored by Leonard Grey's avatar Leonard Grey Committed by Commit Bot

a11y: Announce exclusive access bubble

This is roughly similar to patricialor@'s work in
https://codereview.chromium.org/2010493005/

Per offline conversation,this wasn't submitted
at the time due to some related bugs that are now fixed.

This works well in ChromeVox AFAICT. In VoiceOver,
testing on permission.site, pointer lock works well,
but fullscreen's announcement can sometimes be cut
off by VoiceOver returning to read the rest of the
page. Waiting until the animation is complete helps
a little but doesn't fully fix it, but I'm not sure
what else we could do.

Bug: 610583, 577011
Change-Id: Ic1ef5ba8f81d432eb1f64335be3f6bb6e4ff31f1
Reviewed-on: https://chromium-review.googlesource.com/1015554
Commit-Queue: Leonard Grey <lgrey@chromium.org>
Reviewed-by: default avatarNektarios Paisios <nektar@chromium.org>
Reviewed-by: default avatarElly Fong-Jones <ellyjones@chromium.org>
Cr-Commit-Position: refs/heads/master@{#554476}
parent 16814dc5
......@@ -23,6 +23,7 @@
#include "chrome/browser/ui/views/subtle_notification_view.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/notification_service.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/animation/slide_animation.h"
......@@ -234,6 +235,8 @@ void ExclusiveAccessBubbleViews::AnimationProgressed(
void ExclusiveAccessBubbleViews::AnimationEnded(
const gfx::Animation* animation) {
if (animation_->IsShowing())
GetView()->NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
AnimationProgressed(animation);
}
......@@ -288,6 +291,8 @@ void ExclusiveAccessBubbleViews::Hide() {
}
void ExclusiveAccessBubbleViews::Show() {
if (animation_->IsShowing())
return;
animation_->SetSlideDuration(kSlideInDurationMs);
animation_->Show();
}
......
......@@ -10,6 +10,7 @@
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/views/harmony/chrome_typography.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/insets.h"
......@@ -41,6 +42,9 @@ const int kKeyNamePaddingPx = 5;
// really a dialog, but a dialog title is a good fit.
constexpr int kInstructionTextContext = views::style::CONTEXT_DIALOG_TITLE;
// Delimiter indicating there should be a segment displayed as a keyboard key.
const char kKeyNameDelimiter[] = "|";
} // namespace
// Class containing the instruction text. Contains fancy styling on the keyboard
......@@ -55,6 +59,7 @@ class SubtleNotificationView::InstructionView : public views::View {
SkColor foreground_color,
SkColor background_color);
const base::string16 text() const { return text_; }
void SetText(const base::string16& text);
private:
......@@ -93,8 +98,8 @@ void SubtleNotificationView::InstructionView::SetText(
// Parse |text|, looking for pipe-delimited segment.
std::vector<base::string16> segments =
base::SplitString(text, base::ASCIIToUTF16("|"), base::TRIM_WHITESPACE,
base::SPLIT_WANT_ALL);
base::SplitString(text, base::ASCIIToUTF16(kKeyNameDelimiter),
base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
// SplitString() returns empty strings for zero-length segments, so given an
// even number of pipes, there should always be an odd number of segments.
// The exception is if |text| is entirely empty, in which case the returned
......@@ -203,3 +208,11 @@ views::Widget* SubtleNotificationView::CreatePopupWidget(
return popup;
}
void SubtleNotificationView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kAlert;
base::string16 accessible_name;
base::RemoveChars(instruction_view_->text(),
base::ASCIIToUTF16(kKeyNameDelimiter), &accessible_name);
node_data->SetName(accessible_name);
}
......@@ -36,6 +36,8 @@ class SubtleNotificationView : public views::View {
static views::Widget* CreatePopupWidget(gfx::NativeView parent_view,
SubtleNotificationView* view,
bool accept_events);
// views::View
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
private:
class InstructionView;
......
......@@ -18,6 +18,16 @@
#import "ui/gfx/mac/coordinate_conversion.h"
#include "ui/strings/grit/ui_strings.h"
@interface AXPlatformNodeCocoa (Private)
// Helper function for string attributes that don't require extra processing.
- (NSString*)getStringAttribute:(ax::mojom::StringAttribute)attribute;
// Returns AXValue, or nil if AXValue isn't an NSString.
- (NSString*)getAXValueAsString;
// Returns the text that should be announced for an event with type |eventType|,
// or nil if it shouldn't be announced.
- (NSString*)announcementTextForEvent:(ax::mojom::Event)eventType;
@end
namespace {
using RoleMap = std::map<ax::mojom::Role, NSString*>;
......@@ -231,6 +241,17 @@ const ActionList& GetActionList() {
}
void NotifyMacEvent(AXPlatformNodeCocoa* target, ax::mojom::Event event_type) {
NSString* announcement_text = [target announcementTextForEvent:event_type];
if (announcement_text) {
NSDictionary* notification_info = @{
NSAccessibilityAnnouncementKey : announcement_text,
NSAccessibilityPriorityKey : @(NSAccessibilityPriorityHigh)
};
NSAccessibilityPostNotificationWithUserInfo(
[NSApp mainWindow], NSAccessibilityAnnouncementRequestedNotification,
notification_info);
return;
}
NSAccessibilityPostNotification(
target, [AXPlatformNodeCocoa nativeNotificationFromAXEvent:event_type]);
}
......@@ -252,13 +273,6 @@ bool AlsoUseShowMenuActionForDefaultAction(const ui::AXNodeData& data) {
} // namespace
@interface AXPlatformNodeCocoa ()
// Helper function for string attributes that don't require extra processing.
- (NSString*)getStringAttribute:(ax::mojom::StringAttribute)attribute;
// Returns AXValue, or nil if AXValue isn't an NSString.
- (NSString*)getAXValueAsString;
@end
@implementation AXPlatformNodeCocoa {
ui::AXPlatformNodeBase* node_; // Weak. Retains us.
}
......@@ -317,6 +331,25 @@ bool AlsoUseShowMenuActionForDefaultAction(const ui::AXNodeData& data) {
return [value isKindOfClass:[NSString class]] ? value : nil;
}
- (NSString*)announcementTextForEvent:(ax::mojom::Event)eventType {
if (eventType == ax::mojom::Event::kAlert &&
node_->GetData().role == ax::mojom::Role::kAlert) {
// If there's no explicitly set accessible name, fall back to
// the inner text.
NSString* name =
[self getStringAttribute:ax::mojom::StringAttribute::kName];
return [name length] > 0 ? name
: base::SysUTF16ToNSString(node_->GetText());
} else if (eventType == ax::mojom::Event::kLiveRegionChanged &&
node_->GetData().HasStringAttribute(
ax::mojom::StringAttribute::kContainerLiveStatus)) {
// Live regions announce their inner text.
return base::SysUTF16ToNSString(node_->GetText());
}
// Only alerts and live regions have something to announce.
return nil;
}
// NSAccessibility informal protocol implementation.
- (BOOL)accessibilityIsIgnored {
......
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