osr: mac: cefclient: Fix accessibility object lifetime (fixes #3595)

Native accessibility objects now have explicit ownership, detach
safely before node deletion, and are released during view teardown.
This commit is contained in:
Marshall Greenblatt committed 2026-09-11 17:19:57 -04:00
1 parent 9270759d79
commit 49815fbdaa
17 files changed
+347 -73

No files matched your search

+4
View File
@@ -2261,6 +2261,7 @@ if (is_mac) {
gypi_paths2.cefclient_sources_browser +
gypi_paths2.cefclient_sources_common +
gypi_paths2.shared_sources_osr_mac +
gypi_paths2.shared_sources_osr_accessibility +
gypi_paths2.cefclient_sources_mac
deps = [
":libcef_dll_wrapper",
@@ -2483,6 +2484,7 @@ if (is_mac) {
gypi_paths2.shared_sources_mac +
gypi_paths2.ceftests_sources_common +
gypi_paths2.shared_sources_osr_mac +
gypi_paths2.shared_sources_osr_accessibility +
gypi_paths2.ceftests_sources_mac
deps = [
":libcef_dll_wrapper",
@@ -2569,6 +2571,7 @@ if (is_mac) {
gypi_paths2.includes_wrapper_win +
gypi_paths2.shared_sources_win +
gypi_paths2.cefclient_sources_win +
gypi_paths2.shared_sources_osr_accessibility +
gypi_paths2.cefclient_sources_resources_win_rc
# Set /SUBSYSTEM:WINDOWS.
@@ -2665,6 +2668,7 @@ if (is_mac) {
gypi_paths2.cefclient_sources_common +
gypi_paths2.cefclient_sources_renderer +
gypi_paths2.cefclient_sources_win +
gypi_paths2.shared_sources_osr_accessibility +
gypi_paths2.cefclient_sources_resources_win_rc + [
# Generated installer config resource (embedded in client DLL,
# read by bootstrap.exe).
+8 -9
View File
@@ -229,11 +229,18 @@
'tests/shared/process_helper_mac.cc',
],
'shared_sources_osr_mac': [
'tests/shared/browser/osr_accessibility_node_mac.mm',
'tests/shared/browser/osr_begin_frame_timer_mac.h',
'tests/shared/browser/osr_begin_frame_timer_mac.mm',
'tests/shared/browser/osr_renderer_metal.h',
'tests/shared/browser/osr_renderer_metal.mm',
],
'shared_sources_osr_accessibility': [
'tests/shared/browser/osr_accessibility_helper.cc',
'tests/shared/browser/osr_accessibility_helper.h',
'tests/shared/browser/osr_accessibility_node.cc',
'tests/shared/browser/osr_accessibility_node.h',
],
'shared_sources_win': [
'tests/shared/browser/main_message_loop_external_pump_win.cc',
'tests/shared/browser/resource_util_win.cc',
@@ -379,10 +386,6 @@
'tests/cefclient/browser/main_context_impl_win.cc',
'tests/cefclient/browser/main_message_loop_multithreaded_win.cc',
'tests/cefclient/browser/main_message_loop_multithreaded_win.h',
'tests/cefclient/browser/osr_accessibility_helper.cc',
'tests/cefclient/browser/osr_accessibility_helper.h',
'tests/cefclient/browser/osr_accessibility_node.cc',
'tests/cefclient/browser/osr_accessibility_node.h',
'tests/cefclient/browser/osr_accessibility_node_win.cc',
'tests/cefclient/browser/osr_dragdrop_win.cc',
'tests/cefclient/browser/osr_dragdrop_win.h',
@@ -421,11 +424,6 @@
'tests/cefclient/browser/browser_window_std_mac.h',
'tests/cefclient/browser/browser_window_std_mac.mm',
'tests/cefclient/browser/main_context_impl_posix.cc',
'tests/cefclient/browser/osr_accessibility_helper.cc',
'tests/cefclient/browser/osr_accessibility_helper.h',
'tests/cefclient/browser/osr_accessibility_node.cc',
'tests/cefclient/browser/osr_accessibility_node.h',
'tests/cefclient/browser/osr_accessibility_node_mac.mm',
'tests/cefclient/browser/root_window_mac.h',
'tests/cefclient/browser/root_window_mac.mm',
'tests/cefclient/browser/temp_window_mac.h',
@@ -691,6 +689,7 @@
'tests/ceftests/win/ceftests.rc',
],
'ceftests_sources_mac': [
'tests/ceftests/osr_accessibility_unittest_mac.mm',
'tests/ceftests/osr_begin_frame_unittest_mac.mm',
'tests/ceftests/osr_renderer_metal_unittest.mm',
'tests/ceftests/os_rendering_unittest_mac.h',
+2
View File
@@ -53,6 +53,7 @@
'includes': [
'shared_sources_mac',
'shared_sources_osr_mac',
'shared_sources_osr_accessibility',
'cefclient_sources_mac',
],
}}
@@ -72,6 +73,7 @@
'set': 'CEFCLIENT_WINDOWS_SRCS',
'includes': [
'shared_sources_win',
'shared_sources_osr_accessibility',
'cefclient_sources_win',
'cefclient_sources_resources_win_rc',
],
@@ -17,12 +17,12 @@
#include "include/wrapper/cef_closure_task.h"
#include "tests/cefclient/browser/bytes_write_handler.h"
#include "tests/cefclient/browser/main_context.h"
#include "tests/cefclient/browser/osr_accessibility_helper.h"
#include "tests/cefclient/browser/osr_accessibility_node.h"
#include "tests/cefclient/browser/text_input_client_osr_mac.h"
#include "tests/cefclient/browser/util_mac.h"
#include "tests/shared/browser/geometry_util.h"
#include "tests/shared/browser/main_message_loop.h"
#include "tests/shared/browser/osr_accessibility_helper.h"
#include "tests/shared/browser/osr_accessibility_node.h"
#include "tests/shared/browser/osr_begin_frame_timer_mac.h"
#include "tests/shared/browser/osr_renderer_metal.h"
@@ -119,6 +119,7 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) {
}
- (void)dealloc {
[self detach];
[[NSNotificationCenter defaultCenter] removeObserver:self];
#if !__has_feature(objc_arc)
if (text_input_context_osr_mac_) {
@@ -132,6 +133,10 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) {
- (void)detach {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
display_pending_ = false;
// Clear the view's pointer before destruction notifications can reenter it.
auto* accessibility_helper = accessibility_helper_;
accessibility_helper_ = nullptr;
delete accessibility_helper;
renderer_ = nullptr;
browser_window_ = nullptr;
if (text_input_client_) {
@@ -1108,9 +1113,11 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) {
client::OsrAXNode* node = accessibility_helper_->GetRootNode();
// Add Root as first Kid
NSMutableArray* kids = [NSMutableArray arrayWithCapacity:1];
NSObject* child = CAST_CEF_NATIVE_ACCESSIBLE_TO_NSOBJECT(
node->GetNativeAccessibleObject(nullptr));
[kids addObject:child];
if (node) {
NSObject* child = CAST_CEF_NATIVE_ACCESSIBLE_TO_NSOBJECT(
node->GetNativeAccessibleObject(nullptr));
[kids addObject:child];
}
return NSAccessibilityUnignoredChildren(kids);
} else {
return [super accessibilityAttributeValue:attribute];
@@ -6,7 +6,7 @@
// data back and forth between MSAA clients and CefClient renderers.
// Sample implementation based on ui\accessibility\ax_platform_node_win.h
#include "tests/cefclient/browser/osr_accessibility_node.h"
#include "tests/shared/browser/osr_accessibility_node.h"
#if defined(CEF_USE_ATL)
@@ -16,7 +16,7 @@
#include <string>
#include "tests/cefclient/browser/osr_accessibility_helper.h"
#include "tests/shared/browser/osr_accessibility_helper.h"
namespace client {
@@ -234,9 +234,9 @@ struct CefIAccessible : public IAccessible {
STDMETHODIMP GetTypeInfoCount(unsigned int FAR* pctinfo) override;
STDMETHODIMP GetTypeInfo(unsigned int iTInfo,
LCID lcid,
ITypeInfo FAR* FAR* ppTInfo) override;
ITypeInfo FAR * FAR * ppTInfo) override;
STDMETHODIMP GetIDsOfNames(REFIID riid,
OLECHAR FAR* FAR* rgszNames,
OLECHAR FAR * FAR * rgszNames,
unsigned int cNames,
LCID lcid,
DISPID FAR* rgDispId) override;
@@ -673,12 +673,12 @@ STDMETHODIMP CefIAccessible::GetTypeInfoCount(unsigned int FAR* pctinfo) {
STDMETHODIMP CefIAccessible::GetTypeInfo(unsigned int iTInfo,
LCID lcid,
ITypeInfo FAR* FAR* ppTInfo) {
ITypeInfo FAR * FAR * ppTInfo) {
return E_NOTIMPL;
}
STDMETHODIMP CefIAccessible::GetIDsOfNames(REFIID riid,
OLECHAR FAR* FAR* rgszNames,
OLECHAR FAR * FAR * rgszNames,
unsigned int cNames,
LCID lcid,
DISPID FAR* rgDispId) {
+2 -2
View File
@@ -15,14 +15,14 @@
#include "include/base/cef_build.h"
#include "include/views/cef_display.h"
#include "tests/cefclient/browser/main_context.h"
#include "tests/cefclient/browser/osr_accessibility_helper.h"
#include "tests/cefclient/browser/osr_accessibility_node.h"
#include "tests/cefclient/browser/osr_ime_handler_win.h"
#include "tests/cefclient/browser/osr_render_handler_win_d3d11.h"
#include "tests/cefclient/browser/osr_render_handler_win_gl.h"
#include "tests/cefclient/browser/resource.h"
#include "tests/shared/browser/geometry_util.h"
#include "tests/shared/browser/main_message_loop.h"
#include "tests/shared/browser/osr_accessibility_helper.h"
#include "tests/shared/browser/osr_accessibility_node.h"
#include "tests/shared/browser/util_win.h"
namespace client {
+1 -1
View File
@@ -11,10 +11,10 @@
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_helpers.h"
#include "tests/cefclient/browser/client_handler_osr.h"
#include "tests/cefclient/browser/osr_accessibility_node.h"
#include "tests/cefclient/browser/osr_dragdrop_win.h"
#include "tests/cefclient/browser/osr_render_handler_win.h"
#include "tests/cefclient/browser/osr_renderer_settings.h"
#include "tests/shared/browser/osr_accessibility_node.h"
namespace client {
+1
View File
@@ -16,6 +16,7 @@
'shared_sources_linux:LINUX',
'shared_sources_mac:MAC',
'shared_sources_osr_mac:MAC',
'shared_sources_osr_accessibility:MAC',
'shared_sources_renderer:WINDOWS',
'shared_sources_renderer:LINUX',
'shared_sources_win:WINDOWS',
@@ -0,0 +1,186 @@
// Copyright (c) 2026 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>
#include <memory>
#include "include/cef_parser.h"
#include "tests/ceftests/thread_helper.h"
#include "tests/gtest/include/gtest/gtest.h"
#include "tests/shared/browser/osr_accessibility_helper.h"
#include "tests/shared/browser/osr_accessibility_node.h"
// Exercise the legacy NSAccessibility entry points implemented by the sample.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// Observe actual object deallocation without retaining the accessibility
// object.
@interface OsrAXLifetimeObserver : NSObject {
@public
int* destruction_count_;
}
@end
@implementation OsrAXLifetimeObserver
- (void)dealloc {
++*destruction_count_;
#if !__has_feature(objc_arc)
[super dealloc];
#endif
}
@end
namespace {
void ObserveLifetime(NSObject* object, int* count) {
static char key;
OsrAXLifetimeObserver* observer = [[OsrAXLifetimeObserver alloc] init];
observer->destruction_count_ = count;
objc_setAssociatedObject(object, &key, observer, OBJC_ASSOCIATION_RETAIN);
#if !__has_feature(objc_arc)
[observer release];
#endif
}
client::OsrAXNode* CreateNode(int id = 1) {
auto value = CefDictionaryValue::Create();
value->SetString("role", "button");
auto attributes = CefDictionaryValue::Create();
attributes->SetString("name", "Test button");
value->SetDictionary("attributes", attributes);
return client::OsrAXNode::CreateNode("test-tree", id, value, nullptr);
}
NSObject* NativeObject(client::OsrAXNode* node,
client::OsrAXNode* parent = nullptr) {
return CAST_CEF_NATIVE_ACCESSIBLE_TO_NSOBJECT(
node->GetNativeAccessibleObject(parent));
}
void NativeObjectSurvivesAutoreleasePoolsImpl() {
int destroyed = 0;
auto* node = CreateNode();
for (int i = 0; i < 3; ++i) {
@autoreleasepool {
NSObject* object = NativeObject(node);
if (i == 0) {
ObserveLifetime(object, &destroyed);
}
EXPECT_TRUE([NSAccessibilityButtonRole
isEqual:[object accessibilityAttributeValue:
NSAccessibilityRoleAttribute]]);
EXPECT_TRUE(
[@"Test button" isEqual:[object accessibilityAttributeValue:
NSAccessibilityTitleAttribute]]);
}
// Without ownership, the first pool drain frees the cached native object.
ASSERT_EQ(0, destroyed);
}
@autoreleasepool {
node->Destroy();
}
EXPECT_EQ(1, destroyed);
}
void RetainedNativeObjectDetachesImpl() {
int destroyed = 0;
CFTypeRef retained;
@autoreleasepool {
auto* node = CreateNode();
NSObject* object = NativeObject(node);
ObserveLifetime(object, &destroyed);
retained = CFRetain((__bridge CFTypeRef)object);
node->Destroy();
}
EXPECT_EQ(0, destroyed);
@autoreleasepool {
NSObject* object = (__bridge NSObject*)retained;
EXPECT_TRUE([object accessibilityIsIgnored]);
for (NSString* attribute in [object accessibilityAttributeNames]) {
EXPECT_EQ(nil, [object accessibilityAttributeValue:attribute]);
}
EXPECT_EQ(nil, [object accessibilityFocusedUIElement]);
EXPECT_EQ(nil, [object accessibilityHitTest:NSZeroPoint]);
EXPECT_EQ(0U, [[object accessibilityActionNames] count]);
EXPECT_EQ(
nil,
[object accessibilityActionDescription:NSAccessibilityPressAction]);
[object accessibilityPerformAction:NSAccessibilityPressAction];
[object accessibilityPerformAction:NSAccessibilityShowMenuAction];
CFRelease(retained);
}
EXPECT_EQ(1, destroyed);
}
void NativeParentCanChangeImpl() {
@autoreleasepool {
auto* first = CreateNode(1);
auto* second = CreateNode(2);
auto* child = CreateNode(3);
NSObject* first_object = NativeObject(first);
NSObject* second_object = NativeObject(second);
// A focus query can create the child before its parent is assigned.
NSObject* child_object = NativeObject(child);
EXPECT_EQ(child_object, NativeObject(child, first));
EXPECT_EQ(first_object,
[child_object
accessibilityAttributeValue:NSAccessibilityParentAttribute]);
EXPECT_EQ(child_object, NativeObject(child, second));
first->Destroy();
EXPECT_EQ(second_object,
[child_object
accessibilityAttributeValue:NSAccessibilityParentAttribute]);
child->SetParent(nullptr);
second->Destroy();
EXPECT_EQ(nil,
[child_object
accessibilityAttributeValue:NSAccessibilityParentAttribute]);
child->Destroy();
}
}
void HelperTeardownDetachesAllNodesImpl() {
// No browser is needed to verify role queries and teardown of rootless nodes.
auto tree = CefParseJSON(R"({"ax_tree_id":"test-tree","updates":[{
"nodes":[{"id":1,"role":"button"},{"id":2,"role":"button"}]
}]})",
JSON_PARSER_RFC);
auto helper = std::make_unique<client::OsrAccessibilityHelper>(tree, nullptr);
auto* first = helper->GetNode("test-tree", 1);
auto* second = helper->GetNode("test-tree", 2);
ASSERT_NE(nullptr, first);
ASSERT_NE(nullptr, second);
int destroyed = 0;
CFTypeRef retained;
@autoreleasepool {
// Construction does not query the browser/window; parent resolution is
// lazy.
NSObject* object = NativeObject(first);
ObserveLifetime(object, &destroyed);
retained = CFRetain((__bridge CFTypeRef)object);
ObserveLifetime(NativeObject(second), &destroyed);
helper.reset();
}
EXPECT_EQ(1, destroyed);
@autoreleasepool {
NSObject* object = (__bridge NSObject*)retained;
EXPECT_TRUE([object accessibilityIsIgnored]);
EXPECT_EQ(
nil, [object accessibilityAttributeValue:NSAccessibilityRoleAttribute]);
CFRelease(retained);
}
EXPECT_EQ(2, destroyed);
}
} // namespace
UI_THREAD_TEST(OsrAccessibilityTest, NativeObjectSurvivesAutoreleasePools)
UI_THREAD_TEST(OsrAccessibilityTest, RetainedNativeObjectDetaches)
UI_THREAD_TEST(OsrAccessibilityTest, NativeParentCanChange)
UI_THREAD_TEST(OsrAccessibilityTest, HelperTeardownDetachesAllNodes)
#pragma clang diagnostic pop
@@ -2,9 +2,9 @@
// 2013 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 "tests/cefclient/browser/osr_accessibility_helper.h"
#include "tests/shared/browser/osr_accessibility_helper.h"
#include "tests/cefclient/browser/osr_accessibility_node.h"
#include "tests/shared/browser/osr_accessibility_node.h"
namespace client {
@@ -43,6 +43,10 @@ OsrAccessibilityHelper::OsrAccessibilityHelper(CefRefPtr<CefValue> value,
UpdateAccessibilityTree(value);
}
OsrAccessibilityHelper::~OsrAccessibilityHelper() {
Reset();
}
int OsrAccessibilityHelper::CastToInt(CefRefPtr<CefValue> value) {
if (value->GetType() == VTYPE_STRING) {
const std::string& str = value->GetString();
@@ -226,10 +230,22 @@ void OsrAccessibilityHelper::UpdateFocusedNode(const CefString& treeId,
}
void OsrAccessibilityHelper::Reset() {
accessibility_node_map_.clear();
// Remove nodes from lookups before destruction notifications can reenter.
decltype(accessibility_node_map_) trees;
trees.swap(accessibility_node_map_);
root_tree_id_ = "";
focused_tree_id_ = "";
focused_node_id_ = -1;
for (auto& [tree_id, tree] : trees) {
for (auto& [node_id, node] : tree.node_map_) {
node->SetParent(nullptr);
}
}
for (auto& [tree_id, tree] : trees) {
for (auto& [node_id, node] : tree.node_map_) {
node->Destroy();
}
}
}
void OsrAccessibilityHelper::DestroyNode(OsrAXNode* node) {
@@ -2,8 +2,8 @@
// 2013 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 CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_HELPER_H_
#define CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_HELPER_H_
#ifndef CEF_TESTS_SHARED_BROWSER_OSR_ACCESSIBILITY_HELPER_H_
#define CEF_TESTS_SHARED_BROWSER_OSR_ACCESSIBILITY_HELPER_H_
#include <map>
@@ -26,6 +26,7 @@ class OsrAXTree {
void SetRootNodeId(int nodeId) { root_node_id_ = nodeId; }
private:
friend class OsrAccessibilityHelper;
CefString parent_tree_id_;
int root_node_id_ = -1;
std::map<int, OsrAXNode*> node_map_;
@@ -38,6 +39,10 @@ class OsrAccessibilityHelper {
public:
OsrAccessibilityHelper(CefRefPtr<CefValue> value,
CefRefPtr<CefBrowser> browser);
~OsrAccessibilityHelper();
OsrAccessibilityHelper(const OsrAccessibilityHelper&) = delete;
OsrAccessibilityHelper& operator=(const OsrAccessibilityHelper&) = delete;
void UpdateAccessibilityTree(CefRefPtr<CefValue> value);
@@ -78,4 +83,4 @@ class OsrAccessibilityHelper {
} // namespace client
#endif // CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_HELPER_H_
#endif // CEF_TESTS_SHARED_BROWSER_OSR_ACCESSIBILITY_HELPER_H_
@@ -5,9 +5,9 @@
// Base class implementation for CEF Acccessibility node. This is subclassed and
// used by both IAccessible/NSAccessibility protocol implementation.
#include "tests/cefclient/browser/osr_accessibility_node.h"
#include "tests/shared/browser/osr_accessibility_node.h"
#include "tests/cefclient/browser/osr_accessibility_helper.h"
#include "tests/shared/browser/osr_accessibility_helper.h"
namespace client {
@@ -2,8 +2,8 @@
// 2013 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 CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_NODE_H_
#define CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_NODE_H_
#ifndef CEF_TESTS_SHARED_BROWSER_OSR_ACCESSIBILITY_NODE_H_
#define CEF_TESTS_SHARED_BROWSER_OSR_ACCESSIBILITY_NODE_H_
#pragma once
#include <vector>
@@ -120,4 +120,4 @@ class OsrAXNode {
} // namespace client
#endif // CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_NODE_H_
#endif // CEF_TESTS_SHARED_BROWSER_OSR_ACCESSIBILITY_NODE_H_
@@ -5,12 +5,12 @@
// Sample implementation for the NSAccessibility protocol for interacting with
// VoiceOver and other accessibility clients.
#include "tests/cefclient/browser/osr_accessibility_node.h"
#include "tests/shared/browser/osr_accessibility_node.h"
#import <AppKit/NSAccessibility.h>
#import <Cocoa/Cocoa.h>
#include "tests/cefclient/browser/osr_accessibility_helper.h"
#include "tests/shared/browser/osr_accessibility_helper.h"
namespace {
@@ -354,60 +354,64 @@ inline int MiddleY(const CefRect& rect) {
@interface OsrAXNodeObject : NSObject {
// OsrAXNode* proxy object
client::OsrAXNode* node_;
CefNativeAccessible* parent_;
}
- (id)init:(client::OsrAXNode*)node;
+ (OsrAXNodeObject*)elementWithNode:(client::OsrAXNode*)node;
- (id)initWithNode:(client::OsrAXNode*)node;
- (void)detach;
@end
@implementation OsrAXNodeObject
- (id)init:(client::OsrAXNode*)node {
node_ = node;
parent_ = node_->GetParentAccessibleObject();
if (!parent_) {
parent_ = node_->GetWindowHandle();
- (id)initWithNode:(client::OsrAXNode*)node {
if ((self = [super init])) {
node_ = node;
}
return self;
}
+ (OsrAXNodeObject*)elementWithNode:(client::OsrAXNode*)node {
// We manage the release ourself
return [[OsrAXNodeObject alloc] init:node];
- (void)detach {
node_ = nullptr;
}
- (BOOL)isEqual:(id)object {
if ([object isKindOfClass:[OsrAXNodeObject self]]) {
OsrAXNodeObject* other = object;
return (node_ == other->node_);
} else {
return NO;
}
// There is one native wrapper per node. Detached wrappers remain distinct.
return self == object;
}
// Utility methods to map AX information received from renderer
// to platform properties
- (NSString*)axRole {
// Get the Role from CefAccessibilityHelper and Map to NSRole
return AxRoleToNSAxRole(node_->AxRole());
return node_ ? AxRoleToNSAxRole(node_->AxRole()) : nil;
}
- (NSString*)axDescription {
if (!node_) {
return nil;
}
std::string desc = node_->AxDescription();
return [NSString stringWithUTF8String:desc.c_str()];
}
- (NSString*)axName {
if (!node_) {
return nil;
}
std::string desc = node_->AxName();
return [NSString stringWithUTF8String:desc.c_str()];
}
- (NSString*)axValue {
if (!node_) {
return nil;
}
std::string desc = node_->AxValue();
return [NSString stringWithUTF8String:desc.c_str()];
}
- (void)doMouseClick:(cef_mouse_button_type_t)type {
if (!node_) {
return;
}
CefRefPtr<CefBrowser> browser = node_->GetBrowser();
if (browser) {
CefMouseEvent mouse_event;
@@ -422,14 +426,18 @@ inline int MiddleY(const CefRect& rect) {
}
- (NSMutableArray*)getKids {
if (!node_) {
return nil;
}
int numChilds = node_->GetChildCount();
if (numChilds > 0) {
NSMutableArray* kids = [NSMutableArray arrayWithCapacity:numChilds];
for (int index = 0; index < numChilds; index++) {
client::OsrAXNode* child = node_->ChildAtIndex(index);
[kids addObject:child ? CAST_CEF_NATIVE_ACCESSIBLE_TO_NSOBJECT(
child->GetNativeAccessibleObject(node_))
: nil];
if (child) {
[kids addObject:CAST_CEF_NATIVE_ACCESSIBLE_TO_NSOBJECT(
child->GetNativeAccessibleObject(node_))];
}
}
return kids;
}
@@ -437,6 +445,9 @@ inline int MiddleY(const CefRect& rect) {
}
- (NSPoint)position {
if (!node_) {
return NSZeroPoint;
}
CefRect cef_rect = node_->AxLocation();
NSPoint origin = NSMakePoint(cef_rect.x, cef_rect.y);
NSSize size = NSMakeSize(cef_rect.width, cef_rect.height);
@@ -454,6 +465,9 @@ inline int MiddleY(const CefRect& rect) {
}
- (NSSize)size {
if (!node_) {
return NSZeroSize;
}
CefRect cef_rect = node_->AxLocation();
NSRect rect =
NSMakeRect(cef_rect.x, cef_rect.y, cef_rect.width, cef_rect.height);
@@ -469,7 +483,7 @@ inline int MiddleY(const CefRect& rect) {
// attributes
- (BOOL)accessibilityIsIgnored {
return NO;
return node_ == nullptr;
}
- (NSArray*)accessibilityAttributeNames {
@@ -493,10 +507,16 @@ inline int MiddleY(const CefRect& rect) {
}
- (id)accessibilityAttributeValue:(NSString*)attribute {
NSObject* typed_parent = CAST_CEF_NATIVE_ACCESSIBLE_TO_NSOBJECT(parent_);
if (!node_) {
return nil;
}
// Resolve the current parent only while attached. Do not cache an unowned
// native parent pointer, which can outlive the parent or miss reparenting.
CefNativeAccessible* parent = node_->GetParentAccessibleObject();
if (!parent) {
parent = node_->GetWindowHandle();
}
NSObject* typed_parent = CAST_CEF_NATIVE_ACCESSIBLE_TO_NSOBJECT(parent);
if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
return [self axRole];
} else if ([attribute
@@ -535,15 +555,15 @@ inline int MiddleY(const CefRect& rect) {
}
- (id)accessibilityHitTest:(NSPoint)point {
return NSAccessibilityUnignoredAncestor(self);
return node_ ? NSAccessibilityUnignoredAncestor(self) : nil;
}
- (NSArray*)accessibilityActionNames {
return [NSArray arrayWithObject:NSAccessibilityPressAction];
return node_ ? @[ NSAccessibilityPressAction ] : @[];
}
- (NSString*)accessibilityActionDescription:(NSString*)action {
return NSAccessibilityActionDescription(action);
return node_ ? NSAccessibilityActionDescription(action) : nil;
}
- (void)accessibilityPerformAction:(NSString*)action {
@@ -557,13 +577,11 @@ inline int MiddleY(const CefRect& rect) {
}
- (id)accessibilityFocusedUIElement {
return NSAccessibilityUnignoredAncestor(self);
return node_ ? NSAccessibilityUnignoredAncestor(self) : nil;
}
- (BOOL)accessibilityNotifiesWhenDestroyed {
// Indicate that BrowserAccessibilityCocoa will post a notification when it's
// destroyed (see -detach). This allows VoiceOver to do some internal things
// more efficiently.
// OsrAXNode::Destroy posts the notification after detaching this wrapper.
return YES;
}
@@ -590,9 +608,21 @@ void OsrAXNode::NotifyAccessibilityEvent(std::string event_type) const {
void OsrAXNode::Destroy() {
if (platform_accessibility_) {
// Transfer our ownership to a local before notifying clients, which may
// reenter the wrapper or keep it alive after the C++ node is deleted.
#if __has_feature(objc_arc)
OsrAXNodeObject* object =
(__bridge_transfer OsrAXNodeObject*)platform_accessibility_;
#else
OsrAXNodeObject* object = (OsrAXNodeObject*)platform_accessibility_;
#endif
platform_accessibility_ = nullptr;
[object detach];
NSAccessibilityPostNotification(
CAST_CEF_NATIVE_ACCESSIBLE_TO_NSOBJECT(platform_accessibility_),
NSAccessibilityUIElementDestroyedNotification);
object, NSAccessibilityUIElementDestroyedNotification);
#if !__has_feature(objc_arc)
[object release];
#endif
}
delete this;
@@ -601,11 +631,17 @@ void OsrAXNode::Destroy() {
// Create and return NSAccessibility Implementation Object for Mac
CefNativeAccessible* OsrAXNode::GetNativeAccessibleObject(
client::OsrAXNode* parent) {
if (!platform_accessibility_) {
platform_accessibility_ = CAST_NSOBJECT_TO_CEF_NATIVE_ACCESSIBLE(
[OsrAXNodeObject elementWithNode:this]);
if (parent) {
SetParent(parent);
}
if (!platform_accessibility_) {
OsrAXNodeObject* object = [[OsrAXNodeObject alloc] initWithNode:this];
#if __has_feature(objc_arc)
platform_accessibility_ = (__bridge_retained CefNativeAccessible*)object;
#else
platform_accessibility_ = object;
#endif
}
return platform_accessibility_;
}
@@ -96,7 +96,9 @@ cc_library(
# when linking libraries.
filegroup(
name = "SrcsWin",
srcs = srcs_common + srcs_browser + srcs_browser_win + srcs_renderer,
srcs = srcs_common + srcs_browser + srcs_browser_win + srcs_renderer + [
"//tests/shared:OsrAccessibilitySrcsWin",
],
target_compatible_with = [
"@platforms//os:windows",
],
@@ -34,6 +34,10 @@ srcs_osr_mac = [
${shared_sources_osr_mac}
]
srcs_osr_accessibility = [
${shared_sources_osr_accessibility}
]
srcs_browser_mac_ceftests = [
${ceftests_sources_mac_browser_shared}
]
@@ -91,7 +95,7 @@ declare_cc_library(
declare_objc_library(
name = "OsrLibMac",
srcs = srcs_osr_mac,
srcs = srcs_osr_mac + srcs_osr_accessibility,
target_compatible_with = [
"@platforms//os:macos",
],
@@ -133,6 +137,14 @@ declare_objc_library(
# Windows targets.
#
filegroup(
name = "OsrAccessibilitySrcsWin",
srcs = srcs_osr_accessibility,
target_compatible_with = [
"@platforms//os:windows",
],
)
# Include files directly in the declare_exe target. This simplifies the build
# configuration and avoids issues with Windows discarding symbols (like WinMain)
# when linking libraries.
+4
View File
@@ -1268,6 +1268,8 @@ def main(argv=None):
# transfer shared files
transfer_gypi_files(cef_dir, cef_paths2['shared_sources_win'], \
'tests/shared/', shared_dir, options.quiet)
transfer_gypi_files(cef_dir, cef_paths2['shared_sources_osr_accessibility'], \
'tests/shared/', shared_dir, options.quiet)
# transfer cefclient files
transfer_gypi_files(
@@ -1441,6 +1443,8 @@ def main(argv=None):
'tests/shared/', shared_dir, options.quiet)
transfer_gypi_files(cef_dir, cef_paths2['shared_sources_osr_mac'], \
'tests/shared/', shared_dir, options.quiet)
transfer_gypi_files(cef_dir, cef_paths2['shared_sources_osr_accessibility'], \
'tests/shared/', shared_dir, options.quiet)
transfer_gypi_files(cef_dir, cef_paths2['shared_sources_mac_helper'], \
'tests/shared/', shared_dir, options.quiet)