Win32 a11y bridge and platform node delegates (flutter/engine#29829)

* Win32 a11y bridge and platform node delegates

This is the third in a series of patches adding accessibility support
for the Windows embedder. This patch wires in the Accessibility bridge,
and lands the core structure of the Windows FlutterPlatformNodeDelegate
and AccessibilityBridgeDelegate classes, including:

* Instantiating the AccessibilityBridge when the semantics tree is
  enabled.
* Creating FlutterPlatformNodeDelegate wrappers for semantics tree
  nodes.
* Handling custom action updates.
* Building and updating the accessibility tree on semantics updates.
* Returning the native IAccessible objects when queried.

Breaking this out so that the follow-up patches can be reviewed and
landed in smaller, independent chunks.

Issue: https://github.com/flutter/flutter/issues/77838
Issue: https://github.com/flutter/flutter/issues/93928
This commit is contained in:
Chris Bracken 2021-11-29 13:23:42 -08:00 committed by GitHub
parent b44dfd45b3
commit f8dfe2fab4
23 changed files with 624 additions and 16 deletions

View File

@ -1693,6 +1693,10 @@ FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_texture_regi
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_value.h
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_view.h
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/flutter_linux.h
FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_delegate_win32.cc
FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_delegate_win32.h
FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_delegate_winuwp.cc
FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_delegate_winuwp.h
FILE: ../../../flutter/shell/platform/windows/angle_surface_manager.cc
FILE: ../../../flutter/shell/platform/windows/angle_surface_manager.h
FILE: ../../../flutter/shell/platform/windows/client_wrapper/dart_project_unittests.cc
@ -1717,6 +1721,10 @@ FILE: ../../../flutter/shell/platform/windows/dpi_utils_win32_unittests.cc
FILE: ../../../flutter/shell/platform/windows/external_texture_gl.cc
FILE: ../../../flutter/shell/platform/windows/external_texture_gl.h
FILE: ../../../flutter/shell/platform/windows/flutter_key_map.cc
FILE: ../../../flutter/shell/platform/windows/flutter_platform_node_delegate_win32.cc
FILE: ../../../flutter/shell/platform/windows/flutter_platform_node_delegate_win32.h
FILE: ../../../flutter/shell/platform/windows/flutter_platform_node_delegate_winuwp.cc
FILE: ../../../flutter/shell/platform/windows/flutter_platform_node_delegate_winuwp.h
FILE: ../../../flutter/shell/platform/windows/flutter_project_bundle.cc
FILE: ../../../flutter/shell/platform/windows/flutter_project_bundle.h
FILE: ../../../flutter/shell/platform/windows/flutter_project_bundle_unittests.cc

View File

@ -89,8 +89,12 @@ source_set("flutter_windows_source") {
# Target-specific sources.
if (target_os == "winuwp") {
sources += [
"accessibility_bridge_delegate_winuwp.cc",
"accessibility_bridge_delegate_winuwp.h",
"display_helper_winuwp.cc",
"display_helper_winuwp.h",
"flutter_platform_node_delegate_winuwp.cc",
"flutter_platform_node_delegate_winuwp.h",
"flutter_window_winuwp.cc",
"flutter_window_winuwp.h",
"flutter_windows_winuwp.cc",
@ -106,8 +110,12 @@ source_set("flutter_windows_source") {
]
} else {
sources += [
"accessibility_bridge_delegate_win32.cc",
"accessibility_bridge_delegate_win32.h",
"dpi_utils_win32.cc",
"dpi_utils_win32.h",
"flutter_platform_node_delegate_win32.cc",
"flutter_platform_node_delegate_win32.h",
"flutter_window_win32.cc",
"flutter_window_win32.h",
"flutter_windows_win32.cc",
@ -153,7 +161,10 @@ source_set("flutter_windows_source") {
defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]
}
public_deps = [ ":string_conversion" ]
public_deps = [
":string_conversion",
"//flutter/shell/platform/common:common_cpp_accessibility",
]
deps = [
":flutter_windows_headers",
@ -163,7 +174,6 @@ source_set("flutter_windows_source") {
"//flutter/shell/platform/common/client_wrapper:client_wrapper",
"//flutter/shell/platform/embedder:embedder_as_internal_library",
"//flutter/shell/platform/windows/client_wrapper:client_wrapper_windows",
"//flutter/third_party/accessibility",
"//third_party/angle:libEGL_static", # the order of libEGL_static and
# libGLESv2_static is important.. if
# reversed, will cause a linker error

View File

@ -0,0 +1,36 @@
// Copyright 2013 The Flutter 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 "flutter/shell/platform/windows/accessibility_bridge_delegate_win32.h"
#include "flutter/shell/platform/windows/flutter_platform_node_delegate_win32.h"
#include "flutter/shell/platform/windows/flutter_windows_view.h"
#include "flutter/third_party/accessibility/ax/platform/ax_platform_node_delegate_base.h"
namespace flutter {
AccessibilityBridgeDelegateWin32::AccessibilityBridgeDelegateWin32(
FlutterWindowsEngine* engine)
: engine_(engine) {
assert(engine_);
}
void AccessibilityBridgeDelegateWin32::OnAccessibilityEvent(
ui::AXEventGenerator::TargetedEvent targeted_event) {
// TODO(cbracken): https://github.com/flutter/flutter/issues/77838
}
void AccessibilityBridgeDelegateWin32::DispatchAccessibilityAction(
AccessibilityNodeId target,
FlutterSemanticsAction action,
fml::MallocMapping data) {
// TODO(cbracken): https://github.com/flutter/flutter/issues/77838
}
std::shared_ptr<FlutterPlatformNodeDelegate>
AccessibilityBridgeDelegateWin32::CreateFlutterPlatformNodeDelegate() {
return std::make_shared<FlutterPlatformNodeDelegateWin32>(engine_);
}
} // namespace flutter

View File

@ -0,0 +1,47 @@
// Copyright 2013 The Flutter 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 FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_BRIDGE_DELEGATE_H_
#define FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_BRIDGE_DELEGATE_H_
#include "flutter/shell/platform/common/accessibility_bridge.h"
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
namespace flutter {
class FlutterWindowsEngine;
// The Win32 implementation of AccessibilityBridgeDelegate.
//
// Handles requests from the accessibility bridge to interact with Windows
// accessibility APIs. This includes routing accessibility events fired from
// the framework to Windows, routing native Windows accessibility events to the
// framework, and creating Windows-specific FlutterPlatformNodeDelegate objects
// for each node in the semantics tree.
class AccessibilityBridgeDelegateWin32
: public AccessibilityBridge::AccessibilityBridgeDelegate {
public:
explicit AccessibilityBridgeDelegateWin32(FlutterWindowsEngine* engine);
virtual ~AccessibilityBridgeDelegateWin32() = default;
// |AccessibilityBridge::AccessibilityBridgeDelegate|
void OnAccessibilityEvent(
ui::AXEventGenerator::TargetedEvent targeted_event) override;
// |AccessibilityBridge::AccessibilityBridgeDelegate|
void DispatchAccessibilityAction(AccessibilityNodeId target,
FlutterSemanticsAction action,
fml::MallocMapping data) override;
// |AccessibilityBridge::AccessibilityBridgeDelegate|
std::shared_ptr<FlutterPlatformNodeDelegate>
CreateFlutterPlatformNodeDelegate() override;
private:
FlutterWindowsEngine* engine_;
};
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_BRIDGE_DELEGATE_H_

View File

@ -0,0 +1,37 @@
// Copyright 2013 The Flutter 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 "flutter/shell/platform/windows/accessibility_bridge_delegate_winuwp.h"
#include "flutter/shell/platform/windows/flutter_platform_node_delegate_winuwp.h"
#include "flutter/shell/platform/windows/flutter_windows_view.h"
#include "flutter/third_party/accessibility/ax/platform/ax_platform_node_delegate_base.h"
namespace flutter {
AccessibilityBridgeDelegateWinUWP::AccessibilityBridgeDelegateWinUWP(
FlutterWindowsEngine* engine)
: engine_(engine) {
// TODO(cbracken): https://github.com/flutter/flutter/issues/93928
assert(engine_);
}
void AccessibilityBridgeDelegateWinUWP::OnAccessibilityEvent(
ui::AXEventGenerator::TargetedEvent targeted_event) {
// TODO(cbracken): https://github.com/flutter/flutter/issues/93928
}
void AccessibilityBridgeDelegateWinUWP::DispatchAccessibilityAction(
AccessibilityNodeId target,
FlutterSemanticsAction action,
fml::MallocMapping data) {
// TODO(cbracken): https://github.com/flutter/flutter/issues/93928
}
std::shared_ptr<FlutterPlatformNodeDelegate>
AccessibilityBridgeDelegateWinUWP::CreateFlutterPlatformNodeDelegate() {
return std::make_shared<FlutterPlatformNodeDelegateWinUWP>(engine_);
}
} // namespace flutter

View File

@ -0,0 +1,47 @@
// Copyright 2013 The Flutter 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 FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_BRIDGE_DELEGATE_H_
#define FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_BRIDGE_DELEGATE_H_
#include "flutter/shell/platform/common/accessibility_bridge.h"
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
namespace flutter {
class FlutterWindowsEngine;
// The Windows UWP implementation of AccessibilityBridgeDelegate.
//
// Handles requests from the accessibility bridge to interact with Windows
// accessibility APIs. This includes routing accessibility events fired from
// the framework to Windows, routing native Windows accessibility events to the
// framework, and creating Windows-specific FlutterPlatformNodeDelegate objects
// for each node in the semantics tree.
class AccessibilityBridgeDelegateWinUWP
: public AccessibilityBridge::AccessibilityBridgeDelegate {
public:
explicit AccessibilityBridgeDelegateWinUWP(FlutterWindowsEngine* engine);
virtual ~AccessibilityBridgeDelegateWinUWP() = default;
// |AccessibilityBridge::AccessibilityBridgeDelegate|
void OnAccessibilityEvent(
ui::AXEventGenerator::TargetedEvent targeted_event) override;
// |AccessibilityBridge::AccessibilityBridgeDelegate|
void DispatchAccessibilityAction(AccessibilityNodeId target,
FlutterSemanticsAction action,
fml::MallocMapping data) override;
// |AccessibilityBridge::AccessibilityBridgeDelegate|
std::shared_ptr<FlutterPlatformNodeDelegate>
CreateFlutterPlatformNodeDelegate() override;
private:
FlutterWindowsEngine* engine_;
};
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_BRIDGE_DELEGATE_H_

View File

@ -0,0 +1,80 @@
// Copyright 2013 The Flutter 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 <oleacc.h>
#include "flutter/shell/platform/windows/flutter_platform_node_delegate_win32.h"
#include "flutter/shell/platform/windows/flutter_windows_view.h"
namespace flutter {
FlutterPlatformNodeDelegateWin32::FlutterPlatformNodeDelegateWin32(
FlutterWindowsEngine* engine)
: engine_(engine) {
assert(engine_);
}
FlutterPlatformNodeDelegateWin32::~FlutterPlatformNodeDelegateWin32() {
if (ax_platform_node_) {
ax_platform_node_->Destroy();
}
}
// |ui::AXPlatformNodeDelegate|
void FlutterPlatformNodeDelegateWin32::Init(std::weak_ptr<OwnerBridge> bridge,
ui::AXNode* node) {
FlutterPlatformNodeDelegate::Init(bridge, node);
ax_platform_node_ = ui::AXPlatformNode::Create(this);
assert(ax_platform_node_);
}
// |ui::AXPlatformNodeDelegate|
gfx::NativeViewAccessible
FlutterPlatformNodeDelegateWin32::GetNativeViewAccessible() {
assert(ax_platform_node_);
return ax_platform_node_->GetNativeViewAccessible();
}
// |FlutterPlatformNodeDelegate|
gfx::NativeViewAccessible FlutterPlatformNodeDelegateWin32::GetParent() {
gfx::NativeViewAccessible parent = FlutterPlatformNodeDelegate::GetParent();
if (parent) {
return parent;
}
assert(engine_);
FlutterWindowsView* view = engine_->view();
if (!view) {
return nullptr;
}
HWND hwnd = view->GetPlatformWindow();
if (!hwnd) {
return nullptr;
}
IAccessible* iaccessible_parent;
if (SUCCEEDED(::AccessibleObjectFromWindow(
hwnd, OBJID_WINDOW, IID_IAccessible,
reinterpret_cast<void**>(&iaccessible_parent)))) {
return iaccessible_parent;
}
return nullptr;
}
// |FlutterPlatformNodeDelegate|
gfx::Rect FlutterPlatformNodeDelegateWin32::GetBoundsRect(
const ui::AXCoordinateSystem coordinate_system,
const ui::AXClippingBehavior clipping_behavior,
ui::AXOffscreenResult* offscreen_result) const {
gfx::Rect bounds = FlutterPlatformNodeDelegate::GetBoundsRect(
coordinate_system, clipping_behavior, offscreen_result);
POINT origin{bounds.x(), bounds.y()};
POINT extent{bounds.x() + bounds.width(), bounds.y() + bounds.height()};
ClientToScreen(engine_->view()->GetPlatformWindow(), &origin);
ClientToScreen(engine_->view()->GetPlatformWindow(), &extent);
return gfx::Rect(origin.x, origin.y, extent.x - origin.x,
extent.y - origin.y);
}
} // namespace flutter

View File

@ -0,0 +1,47 @@
// Copyright 2013 The Flutter 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 FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_PLATFORM_NODE_DELEGATE_H_
#define FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_PLATFORM_NODE_DELEGATE_H_
#include "flutter/shell/platform/common/flutter_platform_node_delegate.h"
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
#include "flutter/third_party/accessibility/ax/platform/ax_platform_node.h"
namespace flutter {
class FlutterWindowsEngine;
// The Win32 implementation of FlutterPlatformNodeDelegate.
//
// This class implements a wrapper around the Win32 accessibility objects that
// compose the accessibility tree.
class FlutterPlatformNodeDelegateWin32 : public FlutterPlatformNodeDelegate {
public:
explicit FlutterPlatformNodeDelegateWin32(FlutterWindowsEngine* engine);
virtual ~FlutterPlatformNodeDelegateWin32();
// |ui::AXPlatformNodeDelegate|
void Init(std::weak_ptr<OwnerBridge> bridge, ui::AXNode* node) override;
// |ui::AXPlatformNodeDelegate|
gfx::NativeViewAccessible GetNativeViewAccessible() override;
// |FlutterPlatformNodeDelegate|
gfx::NativeViewAccessible GetParent() override;
// |FlutterPlatformNodeDelegate|
gfx::Rect GetBoundsRect(
const ui::AXCoordinateSystem coordinate_system,
const ui::AXClippingBehavior clipping_behavior,
ui::AXOffscreenResult* offscreen_result) const override;
private:
ui::AXPlatformNode* ax_platform_node_;
FlutterWindowsEngine* engine_;
};
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_PLATFORM_NODE_DELEGATE_H_

View File

@ -0,0 +1,67 @@
// Copyright 2013 The Flutter 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 <oleacc.h>
#include "flutter/shell/platform/windows/flutter_platform_node_delegate_winuwp.h"
#include "flutter/shell/platform/windows/flutter_windows_view.h"
namespace flutter {
FlutterPlatformNodeDelegateWinUWP::FlutterPlatformNodeDelegateWinUWP(
FlutterWindowsEngine* engine)
: engine_(engine) {
// TODO(cbracken): https://github.com/flutter/flutter/issues/93928
assert(engine_);
}
FlutterPlatformNodeDelegateWinUWP::~FlutterPlatformNodeDelegateWinUWP() {
if (ax_platform_node_) {
ax_platform_node_->Destroy();
}
}
// |ui::AXPlatformNodeDelegate|
void FlutterPlatformNodeDelegateWinUWP::Init(std::weak_ptr<OwnerBridge> bridge,
ui::AXNode* node) {
FlutterPlatformNodeDelegate::Init(bridge, node);
ax_platform_node_ = ui::AXPlatformNode::Create(this);
assert(ax_platform_node_);
}
// |ui::AXPlatformNodeDelegate|
gfx::NativeViewAccessible
FlutterPlatformNodeDelegateWinUWP::GetNativeViewAccessible() {
assert(ax_platform_node_);
return ax_platform_node_->GetNativeViewAccessible();
}
// |FlutterPlatformNodeDelegate|
gfx::NativeViewAccessible FlutterPlatformNodeDelegateWinUWP::GetParent() {
gfx::NativeViewAccessible parent = FlutterPlatformNodeDelegate::GetParent();
if (parent) {
return parent;
}
assert(engine_);
FlutterWindowsView* view = engine_->view();
if (!view) {
return nullptr;
}
// TODO(cbracken): https://github.com/flutter/flutter/issues/93928
// Use FlutterWindowsView::GetPlatformView to get the root view, and return
// the associated accessibility object.
return nullptr;
}
// |FlutterPlatformNodeDelegate|
gfx::Rect FlutterPlatformNodeDelegateWinUWP::GetBoundsRect(
const ui::AXCoordinateSystem coordinate_system,
const ui::AXClippingBehavior clipping_behavior,
ui::AXOffscreenResult* offscreen_result) const {
// TODO(cbracken): https://github.com/flutter/flutter/issues/93928
return {};
}
} // namespace flutter

View File

@ -0,0 +1,47 @@
// Copyright 2013 The Flutter 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 FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_PLATFORM_NODE_DELEGATE_H_
#define FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_PLATFORM_NODE_DELEGATE_H_
#include "flutter/shell/platform/common/flutter_platform_node_delegate.h"
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
#include "flutter/third_party/accessibility/ax/platform/ax_platform_node.h"
namespace flutter {
class FlutterWindowsEngine;
// The Windows UWP implementation of FlutterPlatformNodeDelegate.
//
// This class implements a wrapper around the Windows UWP accessibility objects
// that compose the accessibility tree.
class FlutterPlatformNodeDelegateWinUWP : public FlutterPlatformNodeDelegate {
public:
explicit FlutterPlatformNodeDelegateWinUWP(FlutterWindowsEngine* engine);
virtual ~FlutterPlatformNodeDelegateWinUWP();
// |ui::AXPlatformNodeDelegate|
void Init(std::weak_ptr<OwnerBridge> bridge, ui::AXNode* node) override;
// |ui::AXPlatformNodeDelegate|
gfx::NativeViewAccessible GetNativeViewAccessible() override;
// |FlutterPlatformNodeDelegate|
gfx::NativeViewAccessible GetParent() override;
// |FlutterPlatformNodeDelegate|
gfx::Rect GetBoundsRect(
const ui::AXCoordinateSystem coordinate_system,
const ui::AXClippingBehavior clipping_behavior,
ui::AXOffscreenResult* offscreen_result) const override;
private:
ui::AXPlatformNode* ax_platform_node_;
FlutterWindowsEngine* engine_;
};
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_PLATFORM_NODE_DELEGATE_H_

View File

@ -258,4 +258,8 @@ bool FlutterWindowWin32::OnBitmapSurfaceUpdated(const void* allocation,
return ret != 0;
}
gfx::NativeViewAccessible FlutterWindowWin32::GetNativeViewAccessible() {
return binding_handler_delegate_->GetNativeViewAccessible();
}
} // namespace flutter

View File

@ -101,6 +101,9 @@ class FlutterWindowWin32 : public WindowWin32, public WindowBindingHandler {
FlutterPointerDeviceKind device_kind,
int32_t device_id) override;
// |WindowWin32|
gfx::NativeViewAccessible GetNativeViewAccessible() override;
// |FlutterWindowBindingHandler|
void SetView(WindowBindingHandlerDelegate* view) override;

View File

@ -217,6 +217,7 @@ class MockWindowBindingHandlerDelegate : public WindowBindingHandlerDelegate {
MOCK_METHOD0(OnComposeEnd, void());
MOCK_METHOD2(OnComposeChange, void(const std::u16string&, int));
MOCK_METHOD1(OnUpdateSemanticsEnabled, void(bool));
MOCK_METHOD0(GetNativeViewAccessible, gfx::NativeViewAccessible());
MOCK_METHOD7(OnScroll,
void(double,
double,

View File

@ -18,6 +18,12 @@
#include "flutter/shell/platform/windows/task_runner.h"
#include "third_party/rapidjson/include/rapidjson/document.h"
#if defined(WINUWP)
#include "flutter/shell/platform/windows/accessibility_bridge_delegate_winuwp.h"
#else
#include "flutter/shell/platform/windows/accessibility_bridge_delegate_win32.h"
#endif // defined(WINUWP)
namespace flutter {
namespace {
@ -260,6 +266,25 @@ bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
host->view()->OnPreEngineRestart();
};
args.update_semantics_node_callback = [](const FlutterSemanticsNode* node,
void* user_data) {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
if (!node || node->id == kFlutterSemanticsNodeIdBatchEnd) {
host->accessibility_bridge_->CommitUpdates();
return;
}
host->accessibility_bridge_->AddFlutterSemanticsNodeUpdate(node);
};
args.update_semantics_custom_action_callback =
[](const FlutterSemanticsCustomAction* action, void* user_data) {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
if (!action || action->id == kFlutterSemanticsNodeIdBatchEnd) {
host->accessibility_bridge_->CommitUpdates();
return;
}
host->accessibility_bridge_->AddFlutterSemanticsCustomActionUpdate(
action);
};
args.custom_task_runners = &custom_task_runners;
@ -452,10 +477,36 @@ bool FlutterWindowsEngine::DispatchSemanticsAction(
}
void FlutterWindowsEngine::UpdateSemanticsEnabled(bool enabled) {
#if defined(WINUWP)
using AccessibilityBridgeDelegateWindows = AccessibilityBridgeDelegateWinUWP;
#else
using AccessibilityBridgeDelegateWindows = AccessibilityBridgeDelegateWin32;
#endif // defined(WINUWP)
if (engine_ && semantics_enabled_ != enabled) {
semantics_enabled_ = enabled;
embedder_api_.UpdateSemanticsEnabled(engine_, enabled);
if (!semantics_enabled_ && accessibility_bridge_) {
accessibility_bridge_.reset();
} else if (semantics_enabled_ && !accessibility_bridge_) {
accessibility_bridge_ = std::make_shared<AccessibilityBridge>(
std::make_unique<AccessibilityBridgeDelegateWindows>(this));
}
}
}
gfx::NativeViewAccessible FlutterWindowsEngine::GetNativeAccessibleFromId(
AccessibilityNodeId id) {
if (!accessibility_bridge_) {
return nullptr;
}
std::shared_ptr<FlutterPlatformNodeDelegate> node_delegate =
accessibility_bridge_->GetFlutterPlatformNodeDelegateFromID(id).lock();
if (!node_delegate) {
return nullptr;
}
return node_delegate->GetNativeViewAccessible();
}
} // namespace flutter

View File

@ -10,6 +10,7 @@
#include <optional>
#include <vector>
#include "flutter/shell/platform/common/accessibility_bridge.h"
#include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h"
#include "flutter/shell/platform/common/client_wrapper/include/flutter/basic_message_channel.h"
#include "flutter/shell/platform/common/incoming_message_dispatcher.h"
@ -93,6 +94,10 @@ class FlutterWindowsEngine {
// rendering using software instead of OpenGL.
AngleSurfaceManager* surface_manager() { return surface_manager_.get(); }
std::weak_ptr<AccessibilityBridge> accessibility_bridge() {
return accessibility_bridge_;
}
#ifndef WINUWP
WindowProcDelegateManagerWin32* window_proc_delegate_manager() {
return window_proc_delegate_manager_.get();
@ -155,6 +160,9 @@ class FlutterWindowsEngine {
// Returns true if the semantics tree is enabled.
bool semantics_enabled() const { return semantics_enabled_; }
// Returns the native accessibility node with the given id.
gfx::NativeViewAccessible GetNativeAccessibleFromId(AccessibilityNodeId id);
private:
// Allows swapping out embedder_api_ calls in tests.
friend class EngineModifier;
@ -214,6 +222,8 @@ class FlutterWindowsEngine {
bool semantics_enabled_ = false;
std::shared_ptr<AccessibilityBridge> accessibility_bridge_;
#ifndef WINUWP
// The manager for WindowProc delegate registration and callbacks.
std::unique_ptr<WindowProcDelegateManagerWin32> window_proc_delegate_manager_;

View File

@ -6,6 +6,7 @@
#include <chrono>
#include "flutter/shell/platform/common/accessibility_bridge.h"
#include "flutter/shell/platform/windows/keyboard_key_channel_handler.h"
#include "flutter/shell/platform/windows/keyboard_key_embedder_handler.h"
#include "flutter/shell/platform/windows/text_input_plugin.h"
@ -252,6 +253,10 @@ void FlutterWindowsView::OnUpdateSemanticsEnabled(bool enabled) {
engine_->UpdateSemanticsEnabled(enabled);
}
gfx::NativeViewAccessible FlutterWindowsView::GetNativeViewAccessible() {
return engine_->GetNativeAccessibleFromId(AccessibilityBridge::kRootNodeId);
}
void FlutterWindowsView::OnCursorRectUpdated(const Rect& rect) {
binding_handler_->OnCursorRectUpdated(rect);
}

View File

@ -161,6 +161,9 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate,
// |WindowBindingHandlerDelegate|
virtual void OnUpdateSemanticsEnabled(bool enabled) override;
// |WindowBindingHandlerDelegate|
virtual gfx::NativeViewAccessible GetNativeViewAccessible() override;
// |TextInputPluginDelegate|
void OnCursorRectUpdated(const Rect& rect) override;

View File

@ -2,6 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/windows/flutter_windows_view.h"
#include <comdef.h>
#include <comutil.h>
#include <oleacc.h>
#include <iostream>
#include <vector>
@ -9,7 +15,6 @@
#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
#include "flutter/shell/platform/windows/flutter_windows_texture_registrar.h"
#include "flutter/shell/platform/windows/flutter_windows_view.h"
#include "flutter/shell/platform/windows/testing/engine_modifier.h"
#include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h"
#include "flutter/shell/platform/windows/testing/test_keyboard.h"
@ -148,5 +153,68 @@ TEST(FlutterWindowsViewTest, EnableSemantics) {
EXPECT_TRUE(semantics_enabled);
}
TEST(FlutterWindowsEngine, AddSemanticsNodeUpdate) {
std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
EngineModifier modifier(engine.get());
modifier.embedder_api().UpdateSemanticsEnabled =
[](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
return kSuccess;
};
auto window_binding_handler =
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
FlutterWindowsView view(std::move(window_binding_handler));
view.SetEngine(std::move(engine));
// Enable semantics to instantiate accessibility bridge.
view.OnUpdateSemanticsEnabled(true);
auto bridge = view.GetEngine()->accessibility_bridge().lock();
ASSERT_TRUE(bridge);
// Add root node.
FlutterSemanticsNode node{sizeof(FlutterSemanticsNode), 0};
node.label = "name";
node.value = "value";
node.platform_view_id = -1;
bridge->AddFlutterSemanticsNodeUpdate(&node);
bridge->CommitUpdates();
// Look up the root windows node delegate.
auto node_delegate = bridge
->GetFlutterPlatformNodeDelegateFromID(
AccessibilityBridge::kRootNodeId)
.lock();
ASSERT_TRUE(node_delegate);
EXPECT_EQ(node_delegate->GetChildCount(), 0);
// Get the native IAccessible object.
IAccessible* native_view = node_delegate->GetNativeViewAccessible();
ASSERT_TRUE(native_view != nullptr);
// Property lookups will be made against this node itself.
VARIANT varchild{};
varchild.vt = VT_I4;
varchild.lVal = CHILDID_SELF;
// Verify node name matches our label.
BSTR bname = nullptr;
ASSERT_EQ(native_view->get_accName(varchild, &bname), S_OK);
std::string name(_com_util::ConvertBSTRToString(bname));
EXPECT_EQ(name, "name");
// Verify node value matches.
BSTR bvalue = nullptr;
ASSERT_EQ(native_view->get_accValue(varchild, &bvalue), S_OK);
std::string value(_com_util::ConvertBSTRToString(bvalue));
EXPECT_EQ(value, "value");
// Verify node type is a group.
VARIANT varrole{};
varrole.vt = VT_I4;
ASSERT_EQ(native_view->get_accRole(varchild, &varrole), S_OK);
EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_STATICTEXT);
}
} // namespace testing
} // namespace flutter

View File

@ -46,6 +46,7 @@ class MockWin32Window : public WindowWin32, public MockMessageQueue {
MOCK_METHOD1(OnText, void(const std::u16string&));
MOCK_METHOD6(OnKey, bool(int, int, int, char32_t, bool, bool));
MOCK_METHOD1(OnUpdateSemanticsEnabled, void(bool));
MOCK_METHOD0(GetNativeViewAccessible, gfx::NativeViewAccessible());
MOCK_METHOD4(OnScroll,
void(double, double, FlutterPointerDeviceKind, int32_t));
MOCK_METHOD0(OnComposeBegin, void());

View File

@ -7,6 +7,7 @@
#include "flutter/shell/platform/common/geometry.h"
#include "flutter/shell/platform/embedder/embedder.h"
#include "flutter/third_party/accessibility/gfx/native_widget_types.h"
namespace flutter {
@ -102,6 +103,9 @@ class WindowBindingHandlerDelegate {
// Notifies delegate that the Flutter semantics tree should be enabled or
// disabled.
virtual void OnUpdateSemanticsEnabled(bool enabled) = 0;
// Returns the root view accessibility node, or nullptr if none.
virtual gfx::NativeViewAccessible GetNativeViewAccessible() = 0;
};
} // namespace flutter

View File

@ -4,7 +4,13 @@
#include "flutter/shell/platform/windows/window_win32.h"
#include "base/win/atl.h" // NOLINT(build/include_order)
#include <imm.h>
#include <oleacc.h>
#include <uiautomationcore.h>
#include <uiautomationcoreapi.h>
#include <wrl/client.h>
#include <cstring>
@ -146,17 +152,19 @@ void WindowWin32::TrackMouseLeaveEvent(HWND hwnd) {
}
}
void WindowWin32::OnGetObject(UINT const message,
WPARAM const wparam,
LPARAM const lparam) {
LRESULT WindowWin32::OnGetObject(UINT const message,
WPARAM const wparam,
LPARAM const lparam) {
LRESULT reference_result = static_cast<LRESULT>(0L);
// Only the lower 32 bits of lparam are valid when checking the object id
// because it sometimes gets sign-extended incorrectly (but not always).
DWORD obj_id = static_cast<DWORD>(static_cast<DWORD_PTR>(lparam));
bool is_uia_request = static_cast<DWORD>(UiaRootObjectId) == obj_id;
bool is_msaa_request = static_cast<DWORD>(OBJID_CLIENT) == obj_id;
if (is_msaa_request) {
if (is_uia_request || is_msaa_request) {
// On Windows, we don't get a notification that the screen reader has been
// enabled or disabled. There is an API to query for screen reader state,
// but that state isn't set by all screen readers, including by Narrator,
@ -166,11 +174,22 @@ void WindowWin32::OnGetObject(UINT const message,
// Instead, we enable semantics in Flutter if Windows issues queries for
// Microsoft Active Accessibility (MSAA) COM objects.
OnUpdateSemanticsEnabled(true);
// TODO(cbracken): https://github.com/flutter/flutter/issues/77838
// Once AccessibilityBridge is wired up, look up the IAccessible
// representing the root view and call LresultFromObject.
}
gfx::NativeViewAccessible root_view = GetNativeViewAccessible();
if (is_uia_request && root_view) {
Microsoft::WRL::ComPtr<IRawElementProviderSimple> root;
root_view->QueryInterface(IID_PPV_ARGS(&root));
LRESULT lresult =
UiaReturnRawElementProvider(window_handle_, wparam, lparam, root.Get());
return lresult;
} else if (is_msaa_request && root_view) {
// Return the IAccessible for the root view.
Microsoft::WRL::ComPtr<IAccessible> root(root_view);
LRESULT lresult = LresultFromObject(IID_IAccessible, wparam, root.Get());
return lresult;
}
return 0;
}
void WindowWin32::OnImeSetContext(UINT const message,
@ -412,9 +431,13 @@ WindowWin32::HandleMessage(UINT const message,
static_cast<double>(WHEEL_DELTA)),
0.0, kFlutterPointerDeviceKindMouse, kDefaultPointerDeviceId);
break;
case WM_GETOBJECT:
OnGetObject(message, wparam, lparam);
case WM_GETOBJECT: {
LRESULT lresult = OnGetObject(message, wparam, lparam);
if (lresult) {
return lresult;
}
break;
}
case WM_INPUTLANGCHANGE:
// TODO(cbracken): pass this to TextInputManager to aid with
// language-specific issues.

View File

@ -16,6 +16,7 @@
#include "flutter/shell/platform/embedder/embedder.h"
#include "flutter/shell/platform/windows/sequential_id_generator.h"
#include "flutter/shell/platform/windows/text_input_manager_win32.h"
#include "flutter/third_party/accessibility/gfx/native_widget_types.h"
namespace flutter {
@ -126,9 +127,9 @@ class WindowWin32 {
//
// The primary use of this function is to supply Windows with wrapped
// semantics objects for use by Windows accessibility.
void OnGetObject(UINT const message,
WPARAM const wparam,
LPARAM const lparam);
LRESULT OnGetObject(UINT const message,
WPARAM const wparam,
LPARAM const lparam);
// Called when IME composing begins.
virtual void OnComposeBegin() = 0;
@ -218,6 +219,9 @@ class WindowWin32 {
// Used to process key messages. Exposed for dependency injection.
virtual uint32_t Win32MapVkToChar(uint32_t virtual_key);
// Returns the root view accessibility node, or nullptr if none.
virtual gfx::NativeViewAccessible GetNativeViewAccessible() = 0;
private:
// Release OS resources associated with window.
void Destroy();

View File

@ -20,6 +20,11 @@
#include "gfx/geometry/rect.h"
#include "gfx/transform.h"
#ifdef _WIN32
// windowx.h defines GetNextSibling as a macro.
#undef GetNextSibling
#endif
namespace ui {
class AXTableInfo;