Announce alerts through SemanticsService on Windows (flutter/engine#37173)

* Corresponds to changes by reverted PR https://github.com/flutter/engine/pull/36966.

* Changes on top of original reverted PR that add IServiceProvider and COM_INTERFACE_ENTRY's to the new node classes.

* self_com_ unused
This commit is contained in:
yaakovschectman
2022-11-03 11:25:51 -04:00
committed by GitHub
parent 7ab5580678
commit d85910aab2
19 changed files with 1008 additions and 3 deletions

View File

@@ -3073,9 +3073,13 @@ 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_alert.cc
FILE: ../../../flutter/shell/platform/windows/accessibility_alert.h
FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_delegate_windows.cc
FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_delegate_windows.h
FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_delegate_windows_unittests.cc
FILE: ../../../flutter/shell/platform/windows/accessibility_root_node.cc
FILE: ../../../flutter/shell/platform/windows/accessibility_root_node.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

View File

@@ -38,8 +38,12 @@ source_set("flutter_windows_headers") {
source_set("flutter_windows_source") {
# Common Windows sources.
sources = [
"accessibility_alert.cc",
"accessibility_alert.h",
"accessibility_bridge_delegate_windows.cc",
"accessibility_bridge_delegate_windows.h",
"accessibility_root_node.cc",
"accessibility_root_node.h",
"angle_surface_manager.cc",
"angle_surface_manager.h",
"cursor_handler.cc",

View File

@@ -0,0 +1,187 @@
// 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_alert.h"
#include "flutter/shell/platform/windows/accessibility_root_node.h"
namespace flutter {
AccessibilityAlert::AccessibilityAlert() : text_(L""), parent_(nullptr) {}
// IAccessible methods.
IFACEMETHODIMP AccessibilityAlert::accHitTest(LONG screen_physical_pixel_x,
LONG screen_physical_pixel_y,
VARIANT* child) {
child->vt = VT_EMPTY;
return S_FALSE;
}
// Performs the object's default action.
IFACEMETHODIMP AccessibilityAlert::accDoDefaultAction(VARIANT var_id) {
return E_FAIL;
}
// Retrieves an IDispatch interface pointer for the specified child.
IFACEMETHODIMP AccessibilityAlert::get_accChild(VARIANT var_child,
IDispatch** disp_child) {
if (V_VT(&var_child) == VT_I4 && V_I4(&var_child) == CHILDID_SELF) {
*disp_child = this;
AddRef();
return S_OK;
}
*disp_child = nullptr;
return E_FAIL;
}
// Retrieves the number of accessible children.
IFACEMETHODIMP AccessibilityAlert::get_accChildCount(LONG* child_count) {
*child_count = 0;
return S_OK;
}
// Retrieves the tooltip description.
IFACEMETHODIMP AccessibilityAlert::get_accDescription(VARIANT var_id,
BSTR* desc) {
*desc = SysAllocString(text_.c_str());
return S_OK;
}
// Retrieves the name of the specified object.
IFACEMETHODIMP AccessibilityAlert::get_accName(VARIANT var_id, BSTR* name) {
*name = SysAllocString(text_.c_str());
return S_OK;
}
// Retrieves the IDispatch interface of the object's parent.
IFACEMETHODIMP AccessibilityAlert::get_accParent(IDispatch** disp_parent) {
*disp_parent = parent_;
if (*disp_parent) {
(*disp_parent)->AddRef();
return S_OK;
}
return S_FALSE;
}
// Retrieves information describing the role of the specified object.
IFACEMETHODIMP AccessibilityAlert::get_accRole(VARIANT var_id, VARIANT* role) {
*role = {.vt = VT_I4, .lVal = ROLE_SYSTEM_ALERT};
return S_OK;
}
// Retrieves the current state of the specified object.
IFACEMETHODIMP AccessibilityAlert::get_accState(VARIANT var_id,
VARIANT* state) {
*state = {.vt = VT_I4, .lVal = STATE_SYSTEM_DEFAULT};
return S_OK;
}
// Gets the help string for the specified object.
IFACEMETHODIMP AccessibilityAlert::get_accHelp(VARIANT var_id, BSTR* help) {
*help = SysAllocString(L"");
return S_OK;
}
// Retrieve or set the string value associated with the specified object.
// Setting the value is not typically used by screen readers, but it's
// used frequently by automation software.
IFACEMETHODIMP AccessibilityAlert::get_accValue(VARIANT var_id, BSTR* value) {
*value = SysAllocString(text_.c_str());
return S_OK;
}
// IAccessible methods not implemented.
IFACEMETHODIMP AccessibilityAlert::get_accSelection(VARIANT* selected) {
selected->vt = VT_EMPTY;
return E_NOTIMPL;
}
IFACEMETHODIMP AccessibilityAlert::accSelect(LONG flags_sel, VARIANT var_id) {
return E_NOTIMPL;
}
IFACEMETHODIMP AccessibilityAlert::put_accValue(VARIANT var_id,
BSTR new_value) {
return E_NOTIMPL;
}
IFACEMETHODIMP AccessibilityAlert::get_accFocus(VARIANT* focus_child) {
focus_child->vt = VT_EMPTY;
return E_NOTIMPL;
}
IFACEMETHODIMP AccessibilityAlert::get_accHelpTopic(BSTR* help_file,
VARIANT var_id,
LONG* topic_id) {
if (help_file) {
*help_file = nullptr;
}
if (topic_id) {
*topic_id = 0;
}
return E_NOTIMPL;
}
IFACEMETHODIMP AccessibilityAlert::put_accName(VARIANT var_id, BSTR put_name) {
return E_NOTIMPL;
}
IFACEMETHODIMP AccessibilityAlert::get_accKeyboardShortcut(VARIANT var_id,
BSTR* access_key) {
*access_key = nullptr;
return E_NOTIMPL;
}
IFACEMETHODIMP AccessibilityAlert::accLocation(LONG* physical_pixel_left,
LONG* physical_pixel_top,
LONG* width,
LONG* height,
VARIANT var_id) {
return E_NOTIMPL;
}
IFACEMETHODIMP AccessibilityAlert::accNavigate(LONG nav_dir,
VARIANT start,
VARIANT* end) {
end->vt = VT_EMPTY;
return E_NOTIMPL;
}
IFACEMETHODIMP AccessibilityAlert::get_accDefaultAction(VARIANT var_id,
BSTR* default_action) {
*default_action = nullptr;
return E_NOTIMPL;
}
// End of IAccessible methods.
//
// IServiceProvider implementation.
//
IFACEMETHODIMP AccessibilityAlert::QueryService(REFGUID guidService,
REFIID riid,
void** object) {
if (!object) {
return E_INVALIDARG;
}
if (guidService == IID_IAccessible) {
return QueryInterface(riid, object);
}
*object = nullptr;
return E_FAIL;
}
void AccessibilityAlert::SetText(const std::wstring& text) {
text_ = text;
}
void AccessibilityAlert::SetParent(AccessibilityRootNode* parent) {
parent_ = parent;
}
} // namespace flutter

View File

@@ -0,0 +1,123 @@
// 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_ALERT_H_
#define FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_ALERT_H_
#include <atlbase.h>
#include <atlcom.h>
#include <oleacc.h>
#include <string>
namespace flutter {
class AccessibilityRootNode;
// An IAccessible node representing an alert read to the screen reader.
// When an announcement is requested by the framework, an instance of
// this class, if none exists already, is created and made a child of
// the root AccessibilityRootNode node, and is therefore also a sibling
// of the window's root node.
// This node is not interactable to the user.
class __declspec(uuid("778c1bd8-383f-4d49-b6be-8937e12b6a32"))
AccessibilityAlert : public CComObjectRootEx<CComMultiThreadModel>,
public IDispatchImpl<IAccessible>,
public IServiceProvider {
public:
BEGIN_COM_MAP(AccessibilityAlert)
COM_INTERFACE_ENTRY(AccessibilityAlert)
COM_INTERFACE_ENTRY(IAccessible)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IServiceProvider)
END_COM_MAP()
//
// IAccessible methods.
//
// Retrieves the child element or child object at a given point on the screen.
IFACEMETHODIMP accHitTest(LONG screen_physical_pixel_x,
LONG screen_physical_pixel_y,
VARIANT* child) override;
// Retrieves an IDispatch interface pointer for the specified child.
IFACEMETHODIMP get_accChild(VARIANT var_child,
IDispatch** disp_child) override;
// Retrieves the number of accessible children.
IFACEMETHODIMP get_accChildCount(LONG* child_count) override;
// Retrieves a string that describes the object's default action.
IFACEMETHODIMP get_accDefaultAction(VARIANT var_id,
BSTR* default_action) override;
// Retrieves the tooltip description.
IFACEMETHODIMP get_accDescription(VARIANT var_id, BSTR* desc) override;
// Retrieves the name of the specified object.
IFACEMETHODIMP get_accName(VARIANT var_id, BSTR* name) override;
// Retrieves the IDispatch interface of the object's parent.
IFACEMETHODIMP get_accParent(IDispatch** disp_parent) override;
// Retrieves information describing the role of the specified object.
IFACEMETHODIMP get_accRole(VARIANT var_id, VARIANT* role) override;
// Retrieves the current state of the specified object.
IFACEMETHODIMP get_accState(VARIANT var_id, VARIANT* state) override;
// Gets the help string for the specified object.
IFACEMETHODIMP get_accHelp(VARIANT var_id, BSTR* help) override;
// Retrieve the string value associated with the specified object.
IFACEMETHODIMP get_accValue(VARIANT var_id, BSTR* value) override;
// IAccessible methods not implemented.
IFACEMETHODIMP accLocation(LONG* physical_pixel_left,
LONG* physical_pixel_top,
LONG* width,
LONG* height,
VARIANT var_id) override;
IFACEMETHODIMP accNavigate(LONG nav_dir,
VARIANT start,
VARIANT* end) override;
IFACEMETHODIMP accDoDefaultAction(VARIANT var_id) override;
IFACEMETHODIMP get_accFocus(VARIANT* focus_child) override;
IFACEMETHODIMP get_accKeyboardShortcut(VARIANT var_id,
BSTR* access_key) override;
IFACEMETHODIMP get_accSelection(VARIANT* selected) override;
IFACEMETHODIMP accSelect(LONG flags_sel, VARIANT var_id) override;
IFACEMETHODIMP get_accHelpTopic(BSTR* help_file,
VARIANT var_id,
LONG* topic_id) override;
IFACEMETHODIMP put_accName(VARIANT var_id, BSTR put_name) override;
IFACEMETHODIMP put_accValue(VARIANT var_id, BSTR new_value) override;
// End of IAccessible methods.
//
// IServiceProvider method.
//
IFACEMETHODIMP QueryService(REFGUID guidService,
REFIID riid,
void** object) override;
AccessibilityAlert();
~AccessibilityAlert() = default;
// Sets the text of this alert to the provided message.
void SetText(const std::wstring& text);
void SetParent(AccessibilityRootNode* parent);
private:
std::wstring text_;
AccessibilityRootNode* parent_;
};
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_ALERT_H_

View File

@@ -0,0 +1,306 @@
// 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_root_node.h"
#include "flutter/fml/logging.h"
#include "flutter/third_party/accessibility/base/win/atl_module.h"
namespace flutter {
static constexpr LONG kWindowChildId = 1;
static constexpr LONG kInvalidChildId = 3;
AccessibilityRootNode::AccessibilityRootNode() : alert_accessible_(nullptr) {}
AccessibilityRootNode::~AccessibilityRootNode() {
if (alert_accessible_) {
alert_accessible_->Release();
alert_accessible_ = nullptr;
}
}
IAccessible* AccessibilityRootNode::GetTargetAndChildID(VARIANT* var_id) {
LONG& child_id = var_id->lVal;
if (V_VT(var_id) != VT_I4) {
child_id = kInvalidChildId;
return nullptr;
}
child_id = V_I4(var_id);
if (!window_accessible_) {
return nullptr;
}
if (child_id == CHILDID_SELF || child_id == kWindowChildId) {
child_id = CHILDID_SELF;
return window_accessible_;
}
if (child_id == kAlertChildId && alert_accessible_) {
child_id = CHILDID_SELF;
return alert_accessible_;
}
// A negative child ID can be used to refer to an AX node directly by its ID.
if (child_id < 0) {
return window_accessible_;
}
return nullptr;
}
IFACEMETHODIMP AccessibilityRootNode::accHitTest(LONG screen_physical_pixel_x,
LONG screen_physical_pixel_y,
VARIANT* child) {
if (window_accessible_) {
return window_accessible_->accHitTest(screen_physical_pixel_x,
screen_physical_pixel_y, child);
}
child->vt = VT_EMPTY;
return S_FALSE;
}
IFACEMETHODIMP AccessibilityRootNode::accDoDefaultAction(VARIANT var_id) {
IAccessible* target;
if ((target = GetTargetAndChildID(&var_id))) {
return target->accDoDefaultAction(var_id);
}
return E_FAIL;
}
IFACEMETHODIMP AccessibilityRootNode::accLocation(LONG* physical_pixel_left,
LONG* physical_pixel_top,
LONG* width,
LONG* height,
VARIANT var_id) {
IAccessible* target;
if ((target = GetTargetAndChildID(&var_id))) {
return target->accLocation(physical_pixel_left, physical_pixel_top, width,
height, var_id);
}
return S_FALSE;
}
IFACEMETHODIMP AccessibilityRootNode::accNavigate(LONG nav_dir,
VARIANT start,
VARIANT* end) {
IAccessible* target;
if ((target = GetTargetAndChildID(&start))) {
return target->accNavigate(nav_dir, start, end);
}
return S_FALSE;
}
IFACEMETHODIMP AccessibilityRootNode::get_accChild(VARIANT var_child,
IDispatch** disp_child) {
if (V_VT(&var_child) != VT_I4) {
return E_FAIL;
}
LONG child_id = V_I4(&var_child);
if (child_id == CHILDID_SELF) {
*disp_child = this;
} else if (!window_accessible_) {
return E_FAIL;
} else if (child_id == kWindowChildId) {
*disp_child = window_accessible_;
} else if (child_id == kAlertChildId && alert_accessible_) {
*disp_child = alert_accessible_;
} else if (child_id < 0) {
// A negative child ID can be used to refer to an AX node directly by its
// ID.
return window_accessible_->get_accChild(var_child, disp_child);
} else {
return E_FAIL;
}
(*disp_child)->AddRef();
return S_OK;
}
IFACEMETHODIMP AccessibilityRootNode::get_accChildCount(LONG* child_count) {
LONG children = 0;
if (window_accessible_) {
children++;
}
if (alert_accessible_) {
children++;
}
*child_count = children;
return S_OK;
}
IFACEMETHODIMP AccessibilityRootNode::get_accDefaultAction(VARIANT var_id,
BSTR* def_action) {
IAccessible* target;
if ((target = GetTargetAndChildID(&var_id))) {
return target->get_accDefaultAction(var_id, def_action);
}
*def_action = nullptr;
return S_FALSE;
}
IFACEMETHODIMP AccessibilityRootNode::get_accDescription(VARIANT var_id,
BSTR* desc) {
IAccessible* target;
if ((target = GetTargetAndChildID(&var_id))) {
return target->get_accDescription(var_id, desc);
}
return E_FAIL;
}
IFACEMETHODIMP AccessibilityRootNode::get_accFocus(VARIANT* focus_child) {
if (window_accessible_) {
return window_accessible_->get_accFocus(focus_child);
}
focus_child->vt = VT_EMPTY;
return S_OK;
}
IFACEMETHODIMP AccessibilityRootNode::get_accKeyboardShortcut(VARIANT var_id,
BSTR* acc_key) {
IAccessible* target;
if ((target = GetTargetAndChildID(&var_id))) {
return target->get_accKeyboardShortcut(var_id, acc_key);
}
return E_FAIL;
}
IFACEMETHODIMP AccessibilityRootNode::get_accName(VARIANT var_id,
BSTR* name_bstr) {
if (V_I4(&var_id) == CHILDID_SELF) {
std::wstring name = L"ROOT_NODE_VIEW";
*name_bstr = SysAllocString(name.c_str());
return S_OK;
}
IAccessible* target;
if ((target = GetTargetAndChildID(&var_id))) {
return target->get_accName(var_id, name_bstr);
}
return S_FALSE;
}
IFACEMETHODIMP AccessibilityRootNode::get_accParent(IDispatch** disp_parent) {
return S_FALSE;
}
IFACEMETHODIMP AccessibilityRootNode::get_accRole(VARIANT var_id,
VARIANT* role) {
IAccessible* target;
if ((target = GetTargetAndChildID(&var_id))) {
return target->get_accRole(var_id, role);
}
return E_FAIL;
}
IFACEMETHODIMP AccessibilityRootNode::get_accState(VARIANT var_id,
VARIANT* state) {
IAccessible* target;
if ((target = GetTargetAndChildID(&var_id))) {
return target->get_accState(var_id, state);
}
return E_FAIL;
}
IFACEMETHODIMP AccessibilityRootNode::get_accHelp(VARIANT var_id, BSTR* help) {
if (!help) {
return E_INVALIDARG;
}
*help = {};
return S_FALSE;
}
IFACEMETHODIMP AccessibilityRootNode::get_accValue(VARIANT var_id,
BSTR* value) {
IAccessible* target;
if ((target = GetTargetAndChildID(&var_id))) {
return target->get_accValue(var_id, value);
}
return E_FAIL;
}
IFACEMETHODIMP AccessibilityRootNode::put_accValue(VARIANT var_id,
BSTR new_value) {
IAccessible* target;
if ((target = GetTargetAndChildID(&var_id))) {
return target->put_accValue(var_id, new_value);
}
return E_FAIL;
}
IFACEMETHODIMP AccessibilityRootNode::get_accSelection(VARIANT* selected) {
selected->vt = VT_EMPTY;
return S_OK;
}
IFACEMETHODIMP AccessibilityRootNode::accSelect(LONG flagsSelect,
VARIANT var_id) {
IAccessible* target;
if ((target = GetTargetAndChildID(&var_id))) {
return target->accSelect(flagsSelect, var_id);
}
return E_FAIL;
}
IFACEMETHODIMP AccessibilityRootNode::get_accHelpTopic(BSTR* help_file,
VARIANT var_id,
LONG* topic_id) {
if (help_file) {
*help_file = nullptr;
}
if (topic_id) {
*topic_id = -1;
}
return E_NOTIMPL;
}
IFACEMETHODIMP AccessibilityRootNode::put_accName(VARIANT var_id,
BSTR put_name) {
return E_NOTIMPL;
}
//
// IServiceProvider implementation.
//
IFACEMETHODIMP AccessibilityRootNode::QueryService(REFGUID guidService,
REFIID riid,
void** object) {
if (!object) {
return E_INVALIDARG;
}
if (guidService == IID_IAccessible) {
return QueryInterface(riid, object);
}
*object = nullptr;
return E_FAIL;
}
void AccessibilityRootNode::SetWindow(IAccessible* window) {
window_accessible_ = window;
}
AccessibilityAlert* AccessibilityRootNode::GetOrCreateAlert() {
if (!alert_accessible_) {
CComObject<AccessibilityAlert>* instance = nullptr;
HRESULT hr = CComObject<AccessibilityAlert>::CreateInstance(&instance);
if (!SUCCEEDED(hr)) {
FML_LOG(FATAL) << "Failed to create alert accessible";
}
instance->AddRef();
instance->SetParent(this);
alert_accessible_ = instance;
}
return alert_accessible_;
}
// static
AccessibilityRootNode* AccessibilityRootNode::Create() {
ui::win::CreateATLModuleIfNeeded();
CComObject<AccessibilityRootNode>* instance = nullptr;
HRESULT hr = CComObject<AccessibilityRootNode>::CreateInstance(&instance);
if (!SUCCEEDED(hr) || !instance) {
FML_LOG(FATAL) << "Failed to create accessibility root node";
}
instance->AddRef();
return instance;
}
} // namespace flutter

View File

@@ -0,0 +1,137 @@
// 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_ROOT_NODE_H_
#define FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_ROOT_NODE_H_
#include <atlbase.h>
#include <atlcom.h>
#include <oleacc.h>
#include <memory>
#include "flutter/shell/platform/windows/accessibility_alert.h"
namespace flutter {
// A parent node that wraps the window IAccessible node.
class __declspec(uuid("fedb8280-ea4f-47a9-98fe-5d1a557fe4b3"))
AccessibilityRootNode : public CComObjectRootEx<CComMultiThreadModel>,
public IDispatchImpl<IAccessible>,
public IServiceProvider {
public:
static constexpr LONG kAlertChildId = 2;
BEGIN_COM_MAP(AccessibilityRootNode)
COM_INTERFACE_ENTRY(AccessibilityRootNode)
COM_INTERFACE_ENTRY(IAccessible)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IServiceProvider)
END_COM_MAP()
//
// IAccessible methods.
//
// Retrieves the child element or child object at a given point on the screen.
IFACEMETHODIMP accHitTest(LONG screen_physical_pixel_x,
LONG screen_physical_pixel_y,
VARIANT* child) override;
// Performs the object's default action.
IFACEMETHODIMP accDoDefaultAction(VARIANT var_id) override;
// Retrieves the specified object's current screen location.
IFACEMETHODIMP accLocation(LONG* physical_pixel_left,
LONG* physical_pixel_top,
LONG* width,
LONG* height,
VARIANT var_id) override;
// Traverses to another UI element and retrieves the object.
IFACEMETHODIMP accNavigate(LONG nav_dir,
VARIANT start,
VARIANT* end) override;
// Retrieves an IDispatch interface pointer for the specified child.
IFACEMETHODIMP get_accChild(VARIANT var_child,
IDispatch** disp_child) override;
// Retrieves the number of accessible children.
IFACEMETHODIMP get_accChildCount(LONG* child_count) override;
// Retrieves a string that describes the object's default action.
IFACEMETHODIMP get_accDefaultAction(VARIANT var_id,
BSTR* default_action) override;
// Retrieves the tooltip description.
IFACEMETHODIMP get_accDescription(VARIANT var_id, BSTR* desc) override;
// Retrieves the object that has the keyboard focus.
IFACEMETHODIMP get_accFocus(VARIANT* focus_child) override;
// Retrieves the specified object's shortcut.
IFACEMETHODIMP get_accKeyboardShortcut(VARIANT var_id,
BSTR* access_key) override;
// Retrieves the name of the specified object.
IFACEMETHODIMP get_accName(VARIANT var_id, BSTR* name) override;
// Retrieves the IDispatch interface of the object's parent.
IFACEMETHODIMP get_accParent(IDispatch** disp_parent) override;
// Retrieves information describing the role of the specified object.
IFACEMETHODIMP get_accRole(VARIANT var_id, VARIANT* role) override;
// Retrieves the current state of the specified object.
IFACEMETHODIMP get_accState(VARIANT var_id, VARIANT* state) override;
// Gets the help string for the specified object.
IFACEMETHODIMP get_accHelp(VARIANT var_id, BSTR* help) override;
// Retrieve or set the string value associated with the specified object.
// Setting the value is not typically used by screen readers, but it's
// used frequently by automation software.
IFACEMETHODIMP get_accValue(VARIANT var_id, BSTR* value) override;
IFACEMETHODIMP put_accValue(VARIANT var_id, BSTR new_value) override;
// IAccessible methods not implemented.
IFACEMETHODIMP get_accSelection(VARIANT* selected) override;
IFACEMETHODIMP accSelect(LONG flags_sel, VARIANT var_id) override;
IFACEMETHODIMP get_accHelpTopic(BSTR* help_file,
VARIANT var_id,
LONG* topic_id) override;
IFACEMETHODIMP put_accName(VARIANT var_id, BSTR put_name) override;
//
// IServiceProvider method.
//
IFACEMETHODIMP QueryService(REFGUID guidService,
REFIID riid,
void** object) override;
AccessibilityRootNode();
virtual ~AccessibilityRootNode();
void SetWindow(IAccessible* window);
void SetAlert(AccessibilityAlert* alert);
AccessibilityAlert* GetOrCreateAlert();
static AccessibilityRootNode* Create();
private:
// Helper method to redirect method calls to the contained window or alert.
IAccessible* GetTargetAndChildID(VARIANT* var_id);
IAccessible* window_accessible_;
AccessibilityAlert* alert_accessible_;
};
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_ROOT_NODE_H_

View File

@@ -2,8 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io' as io;
import 'dart:typed_data' show ByteData;
import 'dart:typed_data' show ByteData, Uint8List;
import 'dart:ui' as ui;
// Signals a waiting latch in the native test.
@@ -41,6 +42,50 @@ void hiPlatformChannels() {
});
}
@pragma('vm:entry-point')
void alertPlatformChannel() async {
// Serializers for data types are in the framework, so this will be hardcoded.
const int valueMap = 13, valueString = 7;
// Corresponds to:
// Map<String, Object> data =
// {"type": "announce", "data": {"message": ""}};
final Uint8List data = Uint8List.fromList([
valueMap, // _valueMap
2, // Size
// key: "type"
valueString,
'type'.length,
...'type'.codeUnits,
// value: "announce"
valueString,
'announce'.length,
...'announce'.codeUnits,
// key: "data"
valueString,
'data'.length,
...'data'.codeUnits,
// value: map
valueMap, // _valueMap
1, // Size
// key: "message"
valueString,
'message'.length,
...'message'.codeUnits,
// value: ""
valueString,
0, // Length of empty string == 0.
]);
final ByteData byteData = data.buffer.asByteData();
final Completer<ByteData?> enabled = Completer<ByteData?>();
ui.PlatformDispatcher.instance.sendPlatformMessage('semantics', ByteData(0), (ByteData? reply){
enabled.complete(reply);
});
await enabled.future;
ui.PlatformDispatcher.instance.sendPlatformMessage('flutter/accessibility', byteData, (ByteData? _){});
}
@pragma('vm:entry-point')
void customEntrypoint() {}

View File

@@ -291,4 +291,11 @@ void FlutterWindow::SendInitialAccessibilityFeatures() {
OnThemeChange();
}
AccessibilityRootNode* FlutterWindow::GetAccessibilityRootNode() {
if (!accessibility_root_) {
CreateAccessibilityRootNode();
}
return accessibility_root_;
}
} // namespace flutter

View File

@@ -148,6 +148,9 @@ class FlutterWindow : public Window, public WindowBindingHandler {
// |WindowBindingHandler|
void SendInitialAccessibilityFeatures() override;
// |WindowBindingHandler|
AccessibilityRootNode* GetAccessibilityRootNode() override;
private:
// A pointer to a FlutterWindowsView that can be used to update engine
// windowing and input state.

View File

@@ -136,6 +136,7 @@ class MockFlutterWindow : public FlutterWindow {
MOCK_METHOD3(Win32DispatchMessage, UINT(UINT, WPARAM, LPARAM));
MOCK_METHOD4(Win32PeekMessage, BOOL(LPMSG, UINT, UINT, UINT));
MOCK_METHOD1(Win32MapVkToChar, uint32_t(uint32_t));
MOCK_METHOD0(GetPlatformWindow, HWND());
protected:
// |KeyboardManager::WindowDelegate|
@@ -153,10 +154,13 @@ class TestFlutterWindowsView : public FlutterWindowsView {
public:
TestFlutterWindowsView(std::unique_ptr<WindowBindingHandler> window_binding)
: FlutterWindowsView(std::move(window_binding)) {}
~TestFlutterWindowsView() {}
SpyKeyboardKeyHandler* key_event_handler;
SpyTextInputPlugin* text_input_plugin;
MOCK_METHOD4(NotifyWinEventWrapper, void(DWORD, HWND, LONG, LONG));
protected:
std::unique_ptr<KeyboardHandlerBase> CreateKeyboardKeyHandler(
flutter::BinaryMessenger* messenger,
@@ -400,5 +404,37 @@ TEST(FlutterWindowTest, InitialAccessibilityFeatures) {
win32window.SendInitialAccessibilityFeatures();
}
// Ensure that announcing the alert propagates the message to the alert node.
// Different screen readers use different properties for alerts.
TEST(FlutterWindowTest, AlertNode) {
std::unique_ptr<MockFlutterWindow> win32window =
std::make_unique<MockFlutterWindow>();
ON_CALL(*win32window, GetPlatformWindow()).WillByDefault(Return(nullptr));
AccessibilityRootNode* root_node = win32window->GetAccessibilityRootNode();
TestFlutterWindowsView view(std::move(win32window));
EXPECT_CALL(view,
NotifyWinEventWrapper(EVENT_SYSTEM_ALERT, nullptr, OBJID_CLIENT,
AccessibilityRootNode::kAlertChildId))
.Times(1);
std::wstring message = L"Test alert";
view.AnnounceAlert(message);
IAccessible* alert = root_node->GetOrCreateAlert();
VARIANT self{.vt = VT_I4, .lVal = CHILDID_SELF};
BSTR strptr;
alert->get_accName(self, &strptr);
EXPECT_EQ(message, strptr);
alert->get_accDescription(self, &strptr);
EXPECT_EQ(message, strptr);
alert->get_accValue(self, &strptr);
EXPECT_EQ(message, strptr);
VARIANT role;
alert->get_accRole(self, &role);
EXPECT_EQ(role.vt, VT_I4);
EXPECT_EQ(role.lVal, ROLE_SYSTEM_ALERT);
}
} // namespace testing
} // namespace flutter

View File

@@ -14,15 +14,19 @@
#include "flutter/fml/paths.h"
#include "flutter/fml/platform/win/wstring_conversion.h"
#include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h"
#include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_message_codec.h"
#include "flutter/shell/platform/common/path_utils.h"
#include "flutter/shell/platform/windows/accessibility_bridge_delegate_windows.h"
#include "flutter/shell/platform/windows/flutter_windows_view.h"
#include "flutter/shell/platform/windows/system_utils.h"
#include "flutter/shell/platform/windows/task_runner.h"
#include "flutter/third_party/accessibility/ax/ax_node.h"
// winbase.h defines GetCurrentTime as a macro.
#undef GetCurrentTime
static constexpr char kAccessibilityChannelName[] = "flutter/accessibility";
namespace flutter {
namespace {
@@ -183,6 +187,14 @@ FlutterWindowsEngine::FlutterWindowsEngine(
messenger_wrapper_ = std::make_unique<BinaryMessengerImpl>(messenger_.get());
message_dispatcher_ =
std::make_unique<IncomingMessageDispatcher>(messenger_.get());
message_dispatcher_->SetMessageCallback(
kAccessibilityChannelName,
[](FlutterDesktopMessengerRef messenger,
const FlutterDesktopMessage* message, void* data) {
FlutterWindowsEngine* engine = static_cast<FlutterWindowsEngine*>(data);
engine->HandleAccessibilityMessage(messenger, message);
},
static_cast<void*>(this));
FlutterWindowsTextureRegistrar::ResolveGlFunctions(gl_procs_);
texture_registrar_ =
@@ -660,4 +672,25 @@ int FlutterWindowsEngine::EnabledAccessibilityFeatures() const {
return flags;
}
void FlutterWindowsEngine::HandleAccessibilityMessage(
FlutterDesktopMessengerRef messenger,
const FlutterDesktopMessage* message) {
const auto& codec = StandardMessageCodec::GetInstance();
auto data = codec.DecodeMessage(message->message, message->message_size);
EncodableMap map = std::get<EncodableMap>(*data);
std::string type = std::get<std::string>(map.at(EncodableValue("type")));
if (type.compare("announce") == 0) {
if (semantics_enabled_) {
EncodableMap data_map =
std::get<EncodableMap>(map.at(EncodableValue("data")));
std::string text =
std::get<std::string>(data_map.at(EncodableValue("message")));
std::wstring wide_text = fml::Utf8ToWideString(text);
view_->AnnounceAlert(wide_text);
}
}
SendPlatformMessageResponse(message->response_handle,
reinterpret_cast<const uint8_t*>(""), 0);
}
} // namespace flutter

View File

@@ -254,6 +254,9 @@ class FlutterWindowsEngine {
// system changes.
void SendSystemLocales();
void HandleAccessibilityMessage(FlutterDesktopMessengerRef messenger,
const FlutterDesktopMessage* message);
// The handle to the embedder.h engine instance.
FLUTTER_API_SYMBOL(FlutterEngine) engine_ = nullptr;

View File

@@ -6,10 +6,13 @@
#include "flutter/shell/platform/embedder/embedder.h"
#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.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"
#include "flutter/shell/platform/windows/testing/windows_test.h"
#include "fml/synchronization/waitable_event.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
// winbase.h defines GetCurrentTime as a macro.
@@ -516,5 +519,59 @@ TEST_F(FlutterWindowsEngineTest, PostRasterThreadTask) {
EXPECT_TRUE(called);
}
class MockFlutterWindowsView : public FlutterWindowsView {
public:
MockFlutterWindowsView(std::unique_ptr<WindowBindingHandler> wbh)
: FlutterWindowsView(std::move(wbh)) {}
~MockFlutterWindowsView() {}
MOCK_METHOD4(NotifyWinEventWrapper, void(DWORD, HWND, LONG, LONG));
};
TEST_F(FlutterWindowsEngineTest, AlertPlatformMessage) {
FlutterDesktopEngineProperties properties = {};
properties.assets_path = GetContext().GetAssetsPath().c_str();
properties.icu_data_path = GetContext().GetIcuDataPath().c_str();
properties.dart_entrypoint = "alertPlatformChannel";
FlutterProjectBundle project(properties);
auto window_binding_handler =
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
AccessibilityRootNode* root_node = AccessibilityRootNode::Create();
ON_CALL(*window_binding_handler, GetAccessibilityRootNode)
.WillByDefault(::testing::Return(root_node));
MockFlutterWindowsView view(std::move(window_binding_handler));
view.SetEngine(std::make_unique<FlutterWindowsEngine>(project));
FlutterWindowsEngine* engine = view.GetEngine();
EngineModifier modifier(engine);
modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
auto binary_messenger =
std::make_unique<BinaryMessengerImpl>(engine->messenger());
binary_messenger->SetMessageHandler(
"semantics", [&engine](const uint8_t* message, size_t message_size,
BinaryReply reply) {
engine->UpdateSemanticsEnabled(true);
char response[] = "";
reply(reinterpret_cast<uint8_t*>(response), 0);
});
bool did_call = false;
ON_CALL(view, NotifyWinEventWrapper)
.WillByDefault([&did_call](DWORD event, HWND hwnd, LONG obj, LONG child) {
did_call = true;
});
engine->UpdateSemanticsEnabled(true);
engine->Run();
// Rely on timeout mechanism in CI.
while (!did_call) {
engine->task_runner()->ProcessTasks();
}
}
} // namespace testing
} // namespace flutter

View File

@@ -656,4 +656,25 @@ FlutterWindowsEngine* FlutterWindowsView::GetEngine() {
return engine_.get();
}
void FlutterWindowsView::AnnounceAlert(const std::wstring& text) {
AccessibilityRootNode* root_node =
binding_handler_->GetAccessibilityRootNode();
AccessibilityAlert* alert =
binding_handler_->GetAccessibilityRootNode()->GetOrCreateAlert();
alert->SetText(text);
HWND hwnd = GetPlatformWindow();
NotifyWinEventWrapper(EVENT_SYSTEM_ALERT, hwnd, OBJID_CLIENT,
AccessibilityRootNode::kAlertChildId);
}
void FlutterWindowsView::NotifyWinEventWrapper(DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild) {
if (hwnd) {
NotifyWinEvent(EVENT_SYSTEM_ALERT, hwnd, OBJID_CLIENT,
AccessibilityRootNode::kAlertChildId);
}
}
} // namespace flutter

View File

@@ -90,6 +90,9 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate,
// Send the initial accessibility features to the window
void SendInitialAccessibilityFeatures();
// Set the text of the alert, and create it if it does not yet exist.
void AnnounceAlert(const std::wstring& text);
// |WindowBindingHandlerDelegate|
void UpdateHighContrastEnabled(bool enabled) override;
@@ -211,6 +214,11 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate,
virtual std::unique_ptr<TextInputPlugin> CreateTextInputPlugin(
BinaryMessenger* messenger);
virtual void NotifyWinEventWrapper(DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild);
private:
// Struct holding the state of an individual pointer. The engine doesn't keep
// track of which buttons have been pressed, so it's the embedding's

View File

@@ -37,6 +37,7 @@ class MockWindowBindingHandler : public WindowBindingHandler {
bool(const void* allocation, size_t row_bytes, size_t height));
MOCK_METHOD0(GetPrimaryPointerLocation, PointerLocation());
MOCK_METHOD0(SendInitialAccessibilityFeatures, void());
MOCK_METHOD0(GetAccessibilityRootNode, AccessibilityRootNode*());
};
} // namespace testing

View File

@@ -58,7 +58,8 @@ Window::Window(std::unique_ptr<WindowsProcTable> windows_proc_table,
std::unique_ptr<TextInputManager> text_input_manager)
: touch_id_generator_(kMinTouchDeviceId, kMaxTouchDeviceId),
windows_proc_table_(std::move(windows_proc_table)),
text_input_manager_(std::move(text_input_manager)) {
text_input_manager_(std::move(text_input_manager)),
accessibility_root_(nullptr) {
// Get the DPI of the primary monitor as the initial DPI. If Per-Monitor V2 is
// supported, |current_dpi_| should be updated in the
// kWmDpiChangedBeforeParent message.
@@ -210,8 +211,14 @@ LRESULT Window::OnGetObject(UINT const message,
// TODO(cbracken): https://github.com/flutter/flutter/issues/94782
// Implement when we adopt UIA support.
} else if (is_msaa_request && root_view) {
// Create the accessibility root if it does not already exist.
if (!accessibility_root_) {
CreateAccessibilityRootNode();
}
// Return the IAccessible for the root view.
Microsoft::WRL::ComPtr<IAccessible> root(root_view);
// Microsoft::WRL::ComPtr<IAccessible> root(root_view);
accessibility_root_->SetWindow(root_view);
Microsoft::WRL::ComPtr<IAccessible> root(accessibility_root_);
LRESULT lresult = LresultFromObject(IID_IAccessible, wparam, root.Get());
return lresult;
}
@@ -596,6 +603,11 @@ void Window::Destroy() {
window_handle_ = nullptr;
}
if (accessibility_root_) {
accessibility_root_->Release();
accessibility_root_ = nullptr;
}
UnregisterClass(window_class_name_.c_str(), nullptr);
}
@@ -648,4 +660,11 @@ bool Window::GetHighContrastEnabled() {
}
}
void Window::CreateAccessibilityRootNode() {
if (accessibility_root_) {
accessibility_root_->Release();
}
accessibility_root_ = AccessibilityRootNode::Create();
}
} // namespace flutter

View File

@@ -14,6 +14,7 @@
#include <vector>
#include "flutter/shell/platform/embedder/embedder.h"
#include "flutter/shell/platform/windows/accessibility_root_node.h"
#include "flutter/shell/platform/windows/direct_manipulation.h"
#include "flutter/shell/platform/windows/keyboard_manager.h"
#include "flutter/shell/platform/windows/sequential_id_generator.h"
@@ -223,6 +224,9 @@ class Window : public KeyboardManager::WindowDelegate {
// Returns the root view accessibility node, or nullptr if none.
virtual gfx::NativeViewAccessible GetNativeViewAccessible() = 0;
// Create the wrapper node.
void CreateAccessibilityRootNode();
// Handles running DirectManipulation on the window to receive trackpad
// gestures.
std::unique_ptr<DirectManipulationOwner> direct_manipulation_owner_;
@@ -230,6 +234,9 @@ class Window : public KeyboardManager::WindowDelegate {
// Called when a theme change message is issued
virtual void OnThemeChange() = 0;
// A parent node wrapping the window root, used for siblings.
AccessibilityRootNode* accessibility_root_;
private:
// Release OS resources associated with window.
void Destroy();

View File

@@ -11,6 +11,7 @@
#include <variant>
#include "flutter/shell/platform/common/geometry.h"
#include "flutter/shell/platform/windows/accessibility_root_node.h"
#include "flutter/shell/platform/windows/public/flutter_windows.h"
#include "flutter/shell/platform/windows/window_binding_handler_delegate.h"
@@ -92,6 +93,9 @@ class WindowBindingHandler {
// Called to set the initial state of accessibility features
virtual void SendInitialAccessibilityFeatures() = 0;
// Returns the wrapper parent accessibility node.
virtual AccessibilityRootNode* GetAccessibilityRootNode() = 0;
};
} // namespace flutter