Commit 5779245e authored by Leonard Grey's avatar Leonard Grey Committed by Commit Bot

MacViews: Explicitly stitch together content and views a11y hierarchies

Also fixes assumption in AXPlatformNodeCocoa that all children are
also AXPlatformNodeCocoa

Bug: 861756, 833638
Change-Id: I3fbf05858b00f8a8ee06d8c5d0af988252ca26a8
Reviewed-on: https://chromium-review.googlesource.com/1138793
Commit-Queue: Leonard Grey <lgrey@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Reviewed-by: default avatarTrent Apted <tapted@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#576479}
parent a0f9859b
...@@ -18,7 +18,8 @@ ...@@ -18,7 +18,8 @@
#include "content/browser/web_contents/web_contents_view.h" #include "content/browser/web_contents/web_contents_view.h"
#include "content/common/content_export.h" #include "content/common/content_export.h"
#include "content/common/drag_event_source_info.h" #include "content/common/drag_event_source_info.h"
#include "ui/base/cocoa/base_view.h" #import "ui/base/cocoa/accessibility_hostable.h"
#import "ui/base/cocoa/base_view.h"
#include "ui/gfx/geometry/size.h" #include "ui/gfx/geometry/size.h"
@class WebDragDest; @class WebDragDest;
...@@ -40,7 +41,7 @@ class Layer; ...@@ -40,7 +41,7 @@ class Layer;
} }
CONTENT_EXPORT CONTENT_EXPORT
@interface WebContentsViewCocoa : BaseView { @interface WebContentsViewCocoa : BaseView<AccessibilityHostable> {
@private @private
// Instances of this class are owned by both webContentsView_ and AppKit. It // Instances of this class are owned by both webContentsView_ and AppKit. It
// is possible for an instance to outlive its webContentsView_. The // is possible for an instance to outlive its webContentsView_. The
...@@ -48,6 +49,7 @@ CONTENT_EXPORT ...@@ -48,6 +49,7 @@ CONTENT_EXPORT
content::WebContentsViewMac* webContentsView_; content::WebContentsViewMac* webContentsView_;
base::scoped_nsobject<WebDragSource> dragSource_; base::scoped_nsobject<WebDragSource> dragSource_;
base::scoped_nsobject<WebDragDest> dragDest_; base::scoped_nsobject<WebDragDest> dragDest_;
base::scoped_nsobject<id> accessibilityParent_;
BOOL mouseDownCanMoveWindow_; BOOL mouseDownCanMoveWindow_;
} }
...@@ -57,6 +59,7 @@ CONTENT_EXPORT ...@@ -57,6 +59,7 @@ CONTENT_EXPORT
// NSDraggingSource. It is supposedly deprecated, but the non-deprecated API // NSDraggingSource. It is supposedly deprecated, but the non-deprecated API
// -[NSWindow dragImage:...] still relies on it. // -[NSWindow dragImage:...] still relies on it.
- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal; - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal;
@end @end
namespace content { namespace content {
......
...@@ -738,4 +738,18 @@ void WebContentsViewMac::SetParentUiLayer(ui::Layer* parent_ui_layer) { ...@@ -738,4 +738,18 @@ void WebContentsViewMac::SetParentUiLayer(ui::Layer* parent_ui_layer) {
[self updateWebContentsVisibility]; [self updateWebContentsVisibility];
} }
// AccessibilityHostable protocol implementation.
- (void)setAccessibilityParentElement:(id)accessibilityParent {
accessibilityParent_.reset([accessibilityParent retain]);
}
// NSAccessibility informal protocol implementation.
- (id)accessibilityAttributeValue:(NSString*)attribute {
if (accessibilityParent_ &&
[attribute isEqualToString:NSAccessibilityParentAttribute]) {
return accessibilityParent_;
}
return [super accessibilityAttributeValue:attribute];
}
@end @end
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "content/public/test/test_renderer_host.h" #include "content/public/test/test_renderer_host.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "testing/gtest_mac.h"
#include "testing/platform_test.h" #include "testing/platform_test.h"
#include "ui/base/test/cocoa_helper.h" #include "ui/base/test/cocoa_helper.h"
#import "ui/base/test/cocoa_helper.h" #import "ui/base/test/cocoa_helper.h"
...@@ -23,6 +24,7 @@ class WebContentsViewCocoaTest : public ui::CocoaTest { ...@@ -23,6 +24,7 @@ class WebContentsViewCocoaTest : public ui::CocoaTest {
} // namespace } // namespace
TEST_F(WebContentsViewCocoaTest, NonWebDragSourceTest) { TEST_F(WebContentsViewCocoaTest, NonWebDragSourceTest) {
// The designated initializer is private but init should be fine in this case.
base::scoped_nsobject<WebContentsViewCocoa> view( base::scoped_nsobject<WebContentsViewCocoa> view(
[[WebContentsViewCocoa alloc] init]); [[WebContentsViewCocoa alloc] init]);
...@@ -36,6 +38,28 @@ TEST_F(WebContentsViewCocoaTest, NonWebDragSourceTest) { ...@@ -36,6 +38,28 @@ TEST_F(WebContentsViewCocoaTest, NonWebDragSourceTest) {
[view draggingSourceOperationMaskForLocal:NO]); [view draggingSourceOperationMaskForLocal:NO]);
} }
TEST_F(WebContentsViewCocoaTest, AccessibilityParentTest) {
// The designated initializer is private but init should be fine in this case.
base::scoped_nsobject<WebContentsViewCocoa> view(
[[WebContentsViewCocoa alloc] init]);
// NSBox so it participates in the a11y hierarchy.
base::scoped_nsobject<NSView> parent_view([[NSBox alloc] init]);
base::scoped_nsobject<NSView> accessibility_parent([[NSView alloc] init]);
[parent_view addSubview:view];
EXPECT_NSEQ([view accessibilityAttributeValue:NSAccessibilityParentAttribute],
parent_view);
[view setAccessibilityParentElement:accessibility_parent];
EXPECT_NSEQ([view accessibilityAttributeValue:NSAccessibilityParentAttribute],
accessibility_parent);
[view setAccessibilityParentElement:nil];
EXPECT_NSEQ([view accessibilityAttributeValue:NSAccessibilityParentAttribute],
parent_view);
}
namespace { namespace {
class WebContentsViewMacTest : public RenderViewHostTestHarness { class WebContentsViewMacTest : public RenderViewHostTestHarness {
......
...@@ -408,12 +408,15 @@ bool AlsoUseShowMenuActionForDefaultAction(const ui::AXNodeData& data) { ...@@ -408,12 +408,15 @@ bool AlsoUseShowMenuActionForDefaultAction(const ui::AXNodeData& data) {
} }
- (id)accessibilityHitTest:(NSPoint)point { - (id)accessibilityHitTest:(NSPoint)point {
for (AXPlatformNodeCocoa* child in [self AXChildren]) { if (!NSPointInRect(point, [self boundsInScreen]))
if (![child accessibilityIsIgnored] && return nil;
NSPointInRect(point, child.boundsInScreen)) {
return [child accessibilityHitTest:point]; for (id child in [[self AXChildren] reverseObjectEnumerator]) {
} if (id foundChild = [child accessibilityHitTest:point])
return foundChild;
} }
// Hit self, but not any child.
return NSAccessibilityUnignoredAncestor(self); return NSAccessibilityUnignoredAncestor(self);
} }
......
...@@ -87,6 +87,7 @@ component("base") { ...@@ -87,6 +87,7 @@ component("base") {
"clipboard/custom_data_helper_mac.mm", "clipboard/custom_data_helper_mac.mm",
"cocoa/a11y_util.h", "cocoa/a11y_util.h",
"cocoa/a11y_util.mm", "cocoa/a11y_util.mm",
"cocoa/accessibility_hostable.h",
"cocoa/animation_utils.h", "cocoa/animation_utils.h",
"cocoa/appkit_utils.h", "cocoa/appkit_utils.h",
"cocoa/appkit_utils.mm", "cocoa/appkit_utils.mm",
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_BASE_COCOA_ACCESSIBILITY_HOSTABLE_H_
#define UI_BASE_COCOA_ACCESSIBILITY_HOSTABLE_H_
#import <objc/objc.h>
// An object that can be hosted in another accessibility hierarchy.
// This allows for stitching together heterogenous accessibility
// hierarchies, for example the AXPlatformNodeCocoa-based views
// toolkit hierarchy and the BrowserAccessibilityCocoa-based
// web content hierarchy.
@protocol AccessibilityHostable
// Sets |accessibilityParent| as the object returned when the
// receiver is queried for its accessibility parent.
// TODO(lgrey/ellyjones): Remove this in favor of setAccessibilityParent:
// when we switch to the new accessibility API.
- (void)setAccessibilityParentElement:(id)accessibilityParent;
@end
#endif // UI_BASE_COCOA_ACCESSIBILITY_HOSTABLE_H_
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#include "base/mac/foundation_util.h" #include "base/mac/foundation_util.h"
#import "ui/accessibility/platform/ax_platform_node_mac.h"
#import "ui/base/cocoa/accessibility_hostable.h"
#import "ui/views/cocoa/bridged_native_widget.h" #import "ui/views/cocoa/bridged_native_widget.h"
#include "ui/views/controls/native/native_view_host.h" #include "ui/views/controls/native/native_view_host.h"
#include "ui/views/widget/native_widget_mac.h" #include "ui/views/widget/native_widget_mac.h"
...@@ -36,6 +38,17 @@ void EnsureNativeViewHasNoChildWidgets(NSView* native_view) { ...@@ -36,6 +38,17 @@ void EnsureNativeViewHasNoChildWidgets(NSView* native_view) {
} }
} }
AXPlatformNodeCocoa* ClosestPlatformAncestorNode(views::View* view) {
do {
gfx::NativeViewAccessible accessible = view->GetNativeViewAccessible();
if ([accessible isKindOfClass:[AXPlatformNodeCocoa class]]) {
return NSAccessibilityUnignoredAncestor(accessible);
}
view = view->parent();
} while (view);
return nil;
}
} // namespace } // namespace
NativeViewHostMac::NativeViewHostMac(NativeViewHost* host) : host_(host) { NativeViewHostMac::NativeViewHostMac(NativeViewHost* host) : host_(host) {
...@@ -56,6 +69,28 @@ void NativeViewHostMac::AttachNativeView() { ...@@ -56,6 +69,28 @@ void NativeViewHostMac::AttachNativeView() {
if ([native_view_ respondsToSelector:@selector(cr_setParentUiLayer:)]) if ([native_view_ respondsToSelector:@selector(cr_setParentUiLayer:)])
[native_view_ cr_setParentUiLayer:host_->layer()]; [native_view_ cr_setParentUiLayer:host_->layer()];
if ([native_view_ conformsToProtocol:@protocol(AccessibilityHostable)]) {
// Find the closest ancestor view that participates in the views toolkit
// accessibility hierarchy and set its element as the native view's parent.
// This is necessary because a closer ancestor might already be attaching
// to the NSView/content hierarchy.
// For example, web content is currently embedded into the views hierarchy
// roughly like this:
// BrowserView (views)
// |_ WebView (views)
// |_ NativeViewHost (views)
// |_ WebContentView (Cocoa, is |native_view_| in this scenario,
// | accessibility ignored).
// |_ RenderWidgetHostView (Cocoa)
// WebView specifies either the RenderWidgetHostView or the native view as
// its accessibility element. That means that if we were to set it as
// |native_view_|'s parent, the RenderWidgetHostView would be its own
// accessibility parent! Instead, we want to find the browser view and
// attach to its node.
id hostable = native_view_;
[hostable setAccessibilityParentElement:ClosestPlatformAncestorNode(
host_->parent())];
}
EnsureNativeViewHasNoChildWidgets(native_view_); EnsureNativeViewHasNoChildWidgets(native_view_);
BridgedNativeWidget* bridge = NativeWidgetMac::GetBridgeForNativeWindow( BridgedNativeWidget* bridge = NativeWidgetMac::GetBridgeForNativeWindow(
...@@ -85,6 +120,10 @@ void NativeViewHostMac::NativeViewDetaching(bool destroyed) { ...@@ -85,6 +120,10 @@ void NativeViewHostMac::NativeViewDetaching(bool destroyed) {
if ([native_view_ respondsToSelector:@selector(cr_setParentUiLayer:)]) if ([native_view_ respondsToSelector:@selector(cr_setParentUiLayer:)])
[native_view_ cr_setParentUiLayer:nullptr]; [native_view_ cr_setParentUiLayer:nullptr];
if ([native_view_ conformsToProtocol:@protocol(AccessibilityHostable)]) {
id hostable = native_view_;
[hostable setAccessibilityParentElement:nil];
}
EnsureNativeViewHasNoChildWidgets(host_->native_view()); EnsureNativeViewHasNoChildWidgets(host_->native_view());
BridgedNativeWidget* bridge = NativeWidgetMac::GetBridgeForNativeWindow( BridgedNativeWidget* bridge = NativeWidgetMac::GetBridgeForNativeWindow(
...@@ -164,7 +203,7 @@ void NativeViewHostMac::SetFocus() { ...@@ -164,7 +203,7 @@ void NativeViewHostMac::SetFocus() {
} }
gfx::NativeViewAccessible NativeViewHostMac::GetNativeViewAccessible() { gfx::NativeViewAccessible NativeViewHostMac::GetNativeViewAccessible() {
return NULL; return nullptr;
} }
gfx::NativeCursor NativeViewHostMac::GetCursor(int x, int y) { gfx::NativeCursor NativeViewHostMac::GetCursor(int x, int y) {
......
...@@ -12,11 +12,19 @@ ...@@ -12,11 +12,19 @@
#import "base/mac/scoped_nsobject.h" #import "base/mac/scoped_nsobject.h"
#include "base/macros.h" #include "base/macros.h"
#import "testing/gtest_mac.h" #import "testing/gtest_mac.h"
#import "ui/base/cocoa/accessibility_hostable.h"
#include "ui/views/controls/native/native_view_host.h" #include "ui/views/controls/native/native_view_host.h"
#include "ui/views/controls/native/native_view_host_test_base.h" #include "ui/views/controls/native/native_view_host_test_base.h"
#include "ui/views/view.h" #include "ui/views/view.h"
#include "ui/views/widget/widget.h" #include "ui/views/widget/widget.h"
@interface TestAccessibilityHostableView : NSView<AccessibilityHostable>
@property(nonatomic, assign) id accessibilityParentElement;
@end
@implementation TestAccessibilityHostableView
@synthesize accessibilityParentElement = accessibilityParentElement_;
@end
namespace views { namespace views {
class NativeViewHostMacTest : public test::NativeViewHostTestBase { class NativeViewHostMacTest : public test::NativeViewHostTestBase {
...@@ -100,6 +108,24 @@ TEST_F(NativeViewHostMacTest, Attach) { ...@@ -100,6 +108,24 @@ TEST_F(NativeViewHostMacTest, Attach) {
DestroyHost(); DestroyHost();
} }
// Ensure the native view is integrated into the views accessibility
// hierarchy if the native view conforms to the AccessibilityParent
// protocol.
TEST_F(NativeViewHostMacTest, AccessibilityParent) {
CreateHost();
host()->Detach();
base::scoped_nsobject<TestAccessibilityHostableView> view(
[[TestAccessibilityHostableView alloc] init]);
host()->Attach(view);
EXPECT_NSEQ([view accessibilityParentElement],
toplevel()->GetRootView()->GetNativeViewAccessible());
host()->Detach();
DestroyHost();
EXPECT_FALSE([view accessibilityParentElement]);
}
// Test that the content windows' bounds are set to the correct values while the // Test that the content windows' bounds are set to the correct values while the
// native size is equal or not equal to the View size. // native size is equal or not equal to the View size.
TEST_F(NativeViewHostMacTest, ContentViewPositionAndSize) { TEST_F(NativeViewHostMacTest, ContentViewPositionAndSize) {
......
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