Commit 38dabcbb authored by Christopher Cameron's avatar Christopher Cameron Committed by Commit Bot

RemoteMacViews: Hook up remote accessibility interface

Add the NSAccessibilityRemoteUIElement interface along with helper
functions to use the interface with stl structures.

Make the previously-dummy GetNativeViewAccessible implementation of
perform a sync call to BridgedNativeWidgetHost to set up the required
accessibility tree structures.
- Send to the browser process the accessibility tokens for the
  NSWindow and its content NSView.
- Retrieve the accessibility token for the root AXPlatformNodeCocoa,
  along with the browser process id.

This is not sufficient for accessibility to work. The remaining parts
are to
- Update the AXPlatformNodeCocoa class to return the object created
  from the accessibility tokens.
  - Add TODOs where this needs to be hooked up, but defer the actual
    change to a separate patch.
- Splice together the accessibility trees for content and views.

Create a ScopedAccessibilityFocus class to override the value returned
by -[NSApplication accessibilityFocusedUIElement]. This is required for
cross-process accessibility methods to find the appropriate focused
element.

Bug: 900846
Change-Id: I09df0bfddd82b7904131b195ece55dc803c56aa9
Reviewed-on: https://chromium-review.googlesource.com/c/1337377
Commit-Queue: ccameron <ccameron@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarElly Fong-Jones <ellyjones@chromium.org>
Cr-Commit-Position: refs/heads/master@{#608690}
parent a5ded282
......@@ -805,6 +805,9 @@ bool AlsoUseShowMenuActionForDefaultAction(const ui::AXNodeData& data) {
}
- (id)AXWindow {
// TODO(ccameron): This should return the NSAccessibilityRemoteUIElement for
// the NSWindow in the viewer process. This requires that Widget have a method
// to retrieve this object, which does not exist yet.
return node_->GetDelegate()->GetTopLevelWidget().GetNativeNSWindow();
}
......
......@@ -128,6 +128,8 @@ jumbo_component("base") {
"cocoa/ns_view_ids.mm",
"cocoa/quartz_util.h",
"cocoa/quartz_util.mm",
"cocoa/remote_accessibility_api.h",
"cocoa/remote_accessibility_api.mm",
"cocoa/remote_layer_api.h",
"cocoa/remote_layer_api.mm",
"cocoa/secure_password_input.h",
......
// 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_REMOTE_ACCESSIBILITY_API_H_
#define UI_BASE_COCOA_REMOTE_ACCESSIBILITY_API_H_
#import <Cocoa/Cocoa.h>
#include <vector>
#include "base/mac/scoped_nsobject.h"
#include "ui/base/ui_base_export.h"
@interface NSAccessibilityRemoteUIElement : NSObject
+ (void)registerRemoteUIProcessIdentifier:(int)pid;
+ (NSData*)remoteTokenForLocalUIElement:(id)element;
- (id)initWithRemoteToken:(NSData*)token;
@property(retain) id windowUIElement;
@property(retain) id topLevelUIElement;
@end
namespace ui {
// Helper functions to implement the above functions using std::vectors intsead
// of NSData.
class UI_BASE_EXPORT RemoteAccessibility {
public:
static std::vector<uint8_t> GetTokenForLocalElement(id element);
static base::scoped_nsobject<NSAccessibilityRemoteUIElement>
GetRemoteElementFromToken(const std::vector<uint8_t>& token);
};
} // namespace ui
#endif // UI_BASE_COCOA_REMOTE_ACCESSIBILITY_API_H_
// 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.
#include "ui/base/cocoa/remote_accessibility_api.h"
namespace ui {
// static
std::vector<uint8_t> RemoteAccessibility::GetTokenForLocalElement(id element) {
NSData* data =
[NSAccessibilityRemoteUIElement remoteTokenForLocalUIElement:element];
const uint8_t* bytes = reinterpret_cast<const uint8_t*>([data bytes]);
return std::vector<uint8_t>(bytes, bytes + [data length]);
}
// static
base::scoped_nsobject<NSAccessibilityRemoteUIElement>
RemoteAccessibility::GetRemoteElementFromToken(
const std::vector<uint8_t>& token) {
base::scoped_nsobject<NSData> data(
[[NSData alloc] initWithBytes:token.data() length:token.size()]);
return base::scoped_nsobject<NSAccessibilityRemoteUIElement>(
[[NSAccessibilityRemoteUIElement alloc] initWithRemoteToken:data]);
}
} // namespace ui
......@@ -466,6 +466,8 @@ jumbo_component("views") {
"cocoa/bridged_native_widget_host_impl.mm",
"cocoa/drag_drop_client_mac.h",
"cocoa/drag_drop_client_mac.mm",
"cocoa/scoped_accessibility_focus.h",
"cocoa/scoped_accessibility_focus.mm",
"cocoa/tooltip_manager_mac.h",
"cocoa/tooltip_manager_mac.mm",
"controls/button/label_button_label.cc",
......
......@@ -25,6 +25,10 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegateMac::GetParent() {
if (view()->parent())
return view()->parent()->GetNativeViewAccessible();
// TODO(ccameron): This should return the NSAccessibilityRemoteUIElement for
// the NSView in the viewer process. This requires that Widget have a
// method to retrieve this object, which will not necessarily coincide with
// GetNativeView.
if (view()->GetWidget())
return view()->GetWidget()->GetNativeView().GetNativeNSView();
......
......@@ -26,6 +26,7 @@
#include "ui/views_bridge_mac/mojo/bridged_native_widget_host.mojom.h"
@class NativeWidgetMacNSWindow;
@class NSAccessibilityRemoteUIElement;
@class NSView;
namespace ui {
......@@ -36,6 +37,7 @@ namespace views {
class BridgedNativeWidgetImpl;
class NativeWidgetMac;
class ScopedAccessibilityFocus;
// The portion of NativeWidgetMac that lives in the browser process. This
// communicates to the BridgedNativeWidgetImpl, which interacts with the Cocoa
......@@ -88,6 +90,13 @@ class VIEWS_EXPORT BridgedNativeWidgetHostImpl
// NSWindow. Otherwise, it mirrors the id and bounds of the child window.
NativeWidgetMacNSWindow* GetLocalNSWindow() const;
// Return the accessibility object for the parent NSView of the widget's root
// views::View.
gfx::NativeViewAccessible GetParentViewAccessible() const;
// Return the accessibility object for this widget's window.
gfx::NativeViewAccessible GetWindowAccessible() const;
// The mojo interface through which to communicate with the underlying
// NSWindow and NSView.
views_bridge_mac::mojom::BridgedNativeWidget* bridge() const;
......@@ -197,7 +206,7 @@ class VIEWS_EXPORT BridgedNativeWidgetHostImpl
void RankNSViewsRecursive(View* view, std::map<NSView*, int>* rank) const;
// BridgedNativeWidgetHostHelper:
NSView* GetNativeViewAccessible() override;
id GetNativeViewAccessible() override;
void DispatchKeyEvent(ui::KeyEvent* event) override;
bool DispatchKeyEventToMenuController(ui::KeyEvent* event) override;
void GetWordAt(const gfx::Point& location_in_content,
......@@ -287,6 +296,9 @@ class VIEWS_EXPORT BridgedNativeWidgetHostImpl
void GetCanWindowClose(GetCanWindowCloseCallback callback) override;
void GetWindowFrameTitlebarHeight(
GetWindowFrameTitlebarHeightCallback callback) override;
void GetAccessibilityTokens(const std::vector<uint8_t>& window_token,
const std::vector<uint8_t>& view_token,
GetAccessibilityTokensCallback callback) override;
// DialogObserver:
void OnDialogModelChanged() override;
......@@ -331,6 +343,12 @@ class VIEWS_EXPORT BridgedNativeWidgetHostImpl
// process.
views_bridge_mac::mojom::BridgedNativeWidgetAssociatedPtr bridge_ptr_;
// Remote accessibility objects corresponding to the NSWindow and its root
// NSView.
base::scoped_nsobject<NSAccessibilityRemoteUIElement>
remote_window_accessible_;
base::scoped_nsobject<NSAccessibilityRemoteUIElement> remote_view_accessible_;
// TODO(ccameron): Rather than instantiate a BridgedNativeWidgetImpl here,
// we will instantiate a mojo BridgedNativeWidgetImpl interface to a Cocoa
// instance that may be in another process.
......@@ -364,6 +382,10 @@ class VIEWS_EXPORT BridgedNativeWidgetHostImpl
std::unique_ptr<ui::RecyclableCompositorMac> compositor_;
// When allocated, this object will swizzle calls to -[NSApplication
// accessibilityFocusedUIElement] to return GetNativeViewAccessible.
std::unique_ptr<ScopedAccessibilityFocus> scoped_accessibility_focus_;
// Properties used by Set/GetNativeWindowProperty.
std::map<std::string, void*> native_window_properties_;
......
......@@ -8,6 +8,7 @@
#include "base/mac/foundation_util.h"
#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
#include "ui/base/cocoa/remote_accessibility_api.h"
#include "ui/base/hit_test.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/input_method_factory.h"
......@@ -17,6 +18,7 @@
#include "ui/gfx/geometry/dip_util.h"
#include "ui/gfx/mac/coordinate_conversion.h"
#include "ui/native_theme/native_theme_mac.h"
#include "ui/views/cocoa/scoped_accessibility_focus.h"
#include "ui/views/cocoa/tooltip_manager_mac.h"
#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/menu/menu_controller.h"
......@@ -97,6 +99,7 @@ BridgedNativeWidgetHostImpl::BridgedNativeWidgetHostImpl(NativeWidgetMac* owner)
}
BridgedNativeWidgetHostImpl::~BridgedNativeWidgetHostImpl() {
scoped_accessibility_focus_.reset();
DCHECK(children_.empty());
if (bridge_factory_host_) {
bridge_ptr_.reset();
......@@ -124,6 +127,20 @@ NativeWidgetMacNSWindow* BridgedNativeWidgetHostImpl::GetLocalNSWindow() const {
return local_window_.get();
}
gfx::NativeViewAccessible BridgedNativeWidgetHostImpl::GetParentViewAccessible()
const {
if (bridge_impl_)
return bridge_impl_->ns_view();
return remote_view_accessible_.get();
}
gfx::NativeViewAccessible BridgedNativeWidgetHostImpl::GetWindowAccessible()
const {
if (bridge_impl_)
return bridge_impl_->ns_window();
return remote_window_accessible_.get();
}
views_bridge_mac::mojom::BridgedNativeWidget*
BridgedNativeWidgetHostImpl::bridge() const {
if (bridge_ptr_)
......@@ -457,7 +474,7 @@ NSView* BridgedNativeWidgetHostImpl::GetGlobalCaptureView() {
////////////////////////////////////////////////////////////////////////////////
// BridgedNativeWidgetHostImpl, views_bridge_mac::BridgedNativeWidgetHostHelper:
NSView* BridgedNativeWidgetHostImpl::GetNativeViewAccessible() {
id BridgedNativeWidgetHostImpl::GetNativeViewAccessible() {
return root_view_ ? root_view_->GetNativeViewAccessible() : nil;
}
......@@ -772,6 +789,13 @@ void BridgedNativeWidgetHostImpl::OnWindowKeyStatusChanged(
bool is_key,
bool is_content_first_responder,
bool full_keyboard_access_enabled) {
if (is_key) {
scoped_accessibility_focus_ =
std::make_unique<ScopedAccessibilityFocus>(this);
} else {
scoped_accessibility_focus_.reset();
}
is_window_key_ = is_key;
Widget* widget = native_widget_mac_->GetWidget();
if (!widget->OnNativeWidgetActivationChanged(is_key))
......@@ -986,6 +1010,23 @@ void BridgedNativeWidgetHostImpl::GetWindowFrameTitlebarHeight(
std::move(callback).Run(override_titlebar_height, titlebar_height);
}
void BridgedNativeWidgetHostImpl::GetAccessibilityTokens(
const std::vector<uint8_t>& window_token,
const std::vector<uint8_t>& view_token,
GetAccessibilityTokensCallback callback) {
remote_window_accessible_ =
ui::RemoteAccessibility::GetRemoteElementFromToken(window_token);
remote_view_accessible_ =
ui::RemoteAccessibility::GetRemoteElementFromToken(view_token);
[remote_view_accessible_ setWindowUIElement:remote_window_accessible_.get()];
[remote_view_accessible_
setTopLevelUIElement:remote_window_accessible_.get()];
id element_id = GetNativeViewAccessible();
std::move(callback).Run(
getpid(), ui::RemoteAccessibility::GetTokenForLocalElement(element_id));
}
////////////////////////////////////////////////////////////////////////////////
// BridgedNativeWidgetHostImpl, DialogObserver:
......
// 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_VIEWS_COCOA_SCOPED_ACCESSIBILITY_FOCUS_H_
#define UI_VIEWS_COCOA_SCOPED_ACCESSIBILITY_FOCUS_H_
#include "ui/views/views_export.h"
namespace views_bridge_mac {
class BridgedNativeWidgetHostHelper;
} // namespace views_bridge_mac
namespace views {
// This object, while instantiated, will swizzle -[NSApplication
// accessibilityFocusedUIElement] to return the GetNativeViewAccessible of the
// specified BridgedNativeWidgetHostHelper. This is needed to handle remote
// accessibility queries. If two of these objects are instantiated at the same
// time, the most recently created object will take precedence.
class VIEWS_EXPORT ScopedAccessibilityFocus {
public:
ScopedAccessibilityFocus(
views_bridge_mac::BridgedNativeWidgetHostHelper* host_helper);
~ScopedAccessibilityFocus();
private:
views_bridge_mac::BridgedNativeWidgetHostHelper* const host_helper_;
};
} // namespace views
#endif // UI_VIEWS_COCOA_SCOPED_ACCESSIBILITY_FOCUS_H_
// 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.
#include "ui/views/cocoa/scoped_accessibility_focus.h"
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#include "ui/views_bridge_mac/bridged_native_widget_host_helper.h"
namespace views {
namespace {
bool g_has_swizzled_method = false;
views_bridge_mac::BridgedNativeWidgetHostHelper* g_overridden_key_element = nil;
// Swizzled method for -[NSApplication accessibilityFocusedUIElement]. This
// will return the overridden element if one is present. Otherwise, it will
// return the key window.
id SwizzledAccessibilityFocusedUIElement(NSApplication* app, SEL) {
if (g_overridden_key_element) {
return [g_overridden_key_element->GetNativeViewAccessible()
accessibilityFocusedUIElement];
}
return [app keyWindow];
}
} // namespace
ScopedAccessibilityFocus::ScopedAccessibilityFocus(
views_bridge_mac::BridgedNativeWidgetHostHelper* host_helper)
: host_helper_(host_helper) {
if (!g_has_swizzled_method) {
Method method = class_getInstanceMethod(
[NSApplication class], @selector(accessibilityFocusedUIElement));
method_setImplementation(method,
(IMP)SwizzledAccessibilityFocusedUIElement);
g_has_swizzled_method = true;
}
g_overridden_key_element = host_helper_;
}
ScopedAccessibilityFocus::~ScopedAccessibilityFocus() {
if (g_overridden_key_element == host_helper_)
g_overridden_key_element = nullptr;
}
} // namespace views
......@@ -6,6 +6,7 @@
#include "base/no_destructor.h"
#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
#include "ui/base/cocoa/remote_accessibility_api.h"
#include "ui/views_bridge_mac/bridged_native_widget_host_helper.h"
#include "ui/views_bridge_mac/bridged_native_widget_impl.h"
......@@ -36,7 +37,23 @@ class Bridge : public BridgedNativeWidgetHostHelper {
void OnConnectionError() { delete this; }
// BridgedNativeWidgetHostHelper:
NSView* GetNativeViewAccessible() override { return nil; }
id GetNativeViewAccessible() override {
if (!remote_accessibility_element_) {
int64_t browser_pid = 0;
std::vector<uint8_t> element_token;
host_ptr_->GetAccessibilityTokens(
ui::RemoteAccessibility::GetTokenForLocalElement(
bridge_impl_->ns_window()),
ui::RemoteAccessibility::GetTokenForLocalElement(
bridge_impl_->ns_view()),
&browser_pid, &element_token);
[NSAccessibilityRemoteUIElement
registerRemoteUIProcessIdentifier:browser_pid];
remote_accessibility_element_ =
ui::RemoteAccessibility::GetRemoteElementFromToken(element_token);
}
return remote_accessibility_element_.get();
}
void DispatchKeyEvent(ui::KeyEvent* event) override {
bool event_handled = false;
host_ptr_->DispatchKeyEventRemote(std::make_unique<ui::KeyEvent>(*event),
......@@ -68,6 +85,8 @@ class Bridge : public BridgedNativeWidgetHostHelper {
mojom::BridgedNativeWidgetHostAssociatedPtr host_ptr_;
std::unique_ptr<BridgedNativeWidgetImpl> bridge_impl_;
base::scoped_nsobject<NSAccessibilityRemoteUIElement>
remote_accessibility_element_;
};
} // namespace
......
......@@ -25,11 +25,8 @@ class VIEWS_BRIDGE_MAC_EXPORT BridgedNativeWidgetHostHelper {
public:
virtual ~BridgedNativeWidgetHostHelper() = default;
// Retrieve the NSView for accessibility for this widget.
// TODO(ccameron): This interface cannot be implemented over IPC. A scheme
// for implementing accessibility across processes needs to be designed and
// implemented.
virtual NSView* GetNativeViewAccessible() = 0;
// Retrieve the NSObject for accessibility for this widget.
virtual id GetNativeViewAccessible() = 0;
// Synchronously dispatch a key event. Note that this function will modify
// |event| based on whether or not it was handled.
......
......@@ -166,4 +166,13 @@ interface BridgedNativeWidgetHost {
// Handle "Move focus to the window toolbar" shortcut.
OnFocusWindowToolbar();
// Return in |element_token| the token for the AX node for this view. Return
// the pid of the browser process to be registered as a remote UI process. Set
// the AX node for this view to have the element indicated by |window_token|
// as its window and the element indicated by |view_token| as its parent.
[Sync]
GetAccessibilityTokens(array<uint8> window_token,
array<uint8> view_token) =>
(int64 host_pid, array<uint8> element_token);
};
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