| // 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 |