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:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
187
engine/src/flutter/shell/platform/windows/accessibility_alert.cc
Normal file
187
engine/src/flutter/shell/platform/windows/accessibility_alert.cc
Normal 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
|
||||
123
engine/src/flutter/shell/platform/windows/accessibility_alert.h
Normal file
123
engine/src/flutter/shell/platform/windows/accessibility_alert.h
Normal 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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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() {}
|
||||
|
||||
|
||||
@@ -291,4 +291,11 @@ void FlutterWindow::SendInitialAccessibilityFeatures() {
|
||||
OnThemeChange();
|
||||
}
|
||||
|
||||
AccessibilityRootNode* FlutterWindow::GetAccessibilityRootNode() {
|
||||
if (!accessibility_root_) {
|
||||
CreateAccessibilityRootNode();
|
||||
}
|
||||
return accessibility_root_;
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user