| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/accessibility/platform/inspect/ax_call_statement_invoker_win.h" |
| |
| #include "base/logging.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/win/scoped_bstr.h" |
| #include "ui/accessibility/platform/inspect/ax_inspect_utils_win.h" |
| #include "ui/accessibility/platform/inspect/ax_property_node.h" |
| |
| #define DEFINE_IA2_QI_ENTRY(ia2_interface) \ |
| if (interface_name == #ia2_interface) { \ |
| Microsoft::WRL::ComPtr<ia2_interface> obj; \ |
| HRESULT hr = ui::IA2QueryInterface<ia2_interface>(target.Get(), &obj); \ |
| if (hr == S_OK) \ |
| return AXOptionalObject({obj}); \ |
| if (hr == E_NOINTERFACE) \ |
| return AXOptionalObject::Error(interface_name + " is not implemented"); \ |
| return AXOptionalObject::Error("Unexpected error when querying " + \ |
| interface_name); \ |
| } |
| |
| namespace ui { |
| |
| // static |
| std::string AXCallStatementInvokerWin::ToString( |
| const AXOptionalObject& optional) { |
| if (optional.HasValue()) { |
| return optional->ToString(); |
| } |
| |
| if (optional.IsError()) { |
| return "Error:\"" + optional.StateText() + "\""; |
| } |
| return optional.StateToString(); |
| } |
| |
| AXCallStatementInvokerWin::AXCallStatementInvokerWin( |
| const AXTreeIndexerWin* indexer, |
| std::map<std::string, Target>* storage) |
| : indexer_(indexer), storage_(storage) {} |
| |
| AXOptionalObject AXCallStatementInvokerWin::Invoke( |
| const AXPropertyNode& property_node) const { |
| // Executes a scripting statement coded in a given property node. |
| // The statement represents a chainable sequence of attribute calls, where |
| // each subsequent call is invoked on an object returned by a previous call. |
| // For example, p.AXChildren[0].AXRole will unroll into a sequence of |
| // `p.AXChildren`, `(p.AXChildren)[0]` and `((p.AXChildren)[0]).AXRole`. |
| |
| // Get an initial target to invoke an attribute for. First, check the storage |
| // if it has an associated target for the property node, then query the tree |
| // indexer if the property node refers to a DOM id or line index of |
| // an accessible object. If the property node doesn't provide a target then |
| // use the default one (if any, the default node is provided in case of |
| // a tree dumping only, the scripts never have default target). |
| Target target; |
| |
| // Case 1: try to get a target from the storage. The target may refer to |
| // a variable which is kept in the storage. |
| |
| // For example, |
| // `text_parent:= p.parent` will define `text_leaf` variable and put it |
| // into the storage, and then the variable value will be extracted from |
| // the storage for other instruction referring the variable, for example, |
| // `text_parent.role`. |
| if (storage_) { |
| auto storage_iterator = storage_->find(property_node.name_or_value); |
| if (storage_iterator != storage_->end()) { |
| target = storage_iterator->second; |
| |
| if (!IsIAccessibleAndNotNull(target)) { |
| LOG(ERROR) << "Windows invoker only supports IAccessible variable " |
| "assignments."; |
| return AXOptionalObject::Error(); |
| } |
| } |
| } |
| |
| // Case 2: try to get target from the tree indexer. The target may refer to |
| // an accessible element by DOM id or by a line number (:LINE_NUM format) in |
| // a result accessible tree. The tree indexer keeps the mappings between |
| // accessible elements and their DOM ids and line numbers. |
| if (!target) { |
| target = indexer_->NodeBy(property_node.name_or_value); |
| } |
| |
| // Could not find the target. |
| if (!IsIAccessibleAndNotNull(target)) { |
| LOG(ERROR) << "Could not find target: " << property_node.name_or_value; |
| return AXOptionalObject::Error(); |
| } |
| |
| auto* current_node = property_node.next.get(); |
| |
| // Invoke the call chain. |
| while (current_node) { |
| auto target_optional = InvokeFor(target, *current_node); |
| // Result of the current step is state. Don't go any further. |
| if (!target_optional.HasValue()) |
| return target_optional; |
| |
| target = *target_optional; |
| current_node = current_node->next.get(); |
| } |
| |
| // Variable case: store the variable value in the storage. |
| if (!property_node.key.empty()) |
| (*storage_)[property_node.key] = target; |
| |
| return AXOptionalObject(target); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::InvokeFor( |
| const Target& target, |
| const AXPropertyNode& property_node) const { |
| if (target.Is<IAccessibleComPtr>()) |
| return InvokeForAXElement(target.As<IAccessibleComPtr>(), property_node); |
| |
| if (target.Is<IA2ComPtr>()) |
| return InvokeForIA2(target.As<IA2ComPtr>(), property_node); |
| |
| if (target.Is<IA2HypertextComPtr>()) |
| return InvokeForIA2Hypertext(target.As<IA2HypertextComPtr>(), |
| property_node); |
| |
| if (target.Is<IA2TableComPtr>()) |
| return InvokeForIA2Table(target.As<IA2TableComPtr>(), property_node); |
| |
| if (target.Is<IA2TableCellComPtr>()) |
| return InvokeForIA2TableCell(target.As<IA2TableCellComPtr>(), |
| property_node); |
| |
| if (target.Is<IA2TextComPtr>()) |
| return InvokeForIA2Text(target.As<IA2TextComPtr>(), property_node); |
| |
| if (target.Is<IA2TextSelectionContainerComPtr>()) { |
| return InvokeForIA2TextSelectionContainer( |
| target.As<IA2TextSelectionContainerComPtr>(), property_node); |
| } |
| |
| if (target.Is<IA2ValueComPtr>()) |
| return InvokeForIA2Value(target.As<IA2ValueComPtr>(), property_node); |
| |
| LOG(ERROR) << "Unexpected target type for " << property_node.ToFlatString(); |
| return AXOptionalObject::Error(); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::InvokeForAXElement( |
| IAccessibleComPtr target, |
| const AXPropertyNode& property_node) const { |
| if (property_node.name_or_value == "role") { |
| return GetRole(target); |
| } |
| |
| if (property_node.name_or_value == "name") { |
| return GetName(target); |
| } |
| |
| if (property_node.name_or_value == "description") { |
| return GetDescription(target); |
| } |
| |
| if (property_node.name_or_value == "QueryInterface") { |
| if (!property_node.arguments.size()) { |
| LOG(ERROR) << "Error: " << property_node.name_or_value |
| << "called without argument"; |
| return AXOptionalObject::Error(); |
| } |
| std::string interface_name = property_node.arguments[0].name_or_value; |
| return QueryInterface(target, interface_name); |
| } |
| |
| if (property_node.name_or_value == "hasState") { |
| if (!property_node.arguments.size()) { |
| LOG(ERROR) << "Error: " << property_node.name_or_value |
| << "called without argument"; |
| return AXOptionalObject::Error(); |
| } |
| std::string state = property_node.arguments[0].name_or_value; |
| return HasState(target, state); |
| } |
| |
| // Todo: add support for |
| // - [ ] test.accSelection |
| // - [ ] test.get_accSelection |
| // - [ ] test.hasRelation(<relation>) |
| |
| LOG(ERROR) << "Error in '" << property_node.name_or_value |
| << "' called on AXElement in '" << property_node.ToFlatString() |
| << "' statement"; |
| return AXOptionalObject::Error(); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::InvokeForIA2( |
| IA2ComPtr target, |
| const AXPropertyNode& property_node) const { |
| if (property_node.name_or_value == "role") |
| return GetIA2Role(target); |
| |
| if (property_node.name_or_value == "getAttribute") { |
| if (!property_node.arguments.size()) { |
| LOG(ERROR) << "Error: " << property_node.name_or_value |
| << "called without argument"; |
| return AXOptionalObject::Error(); |
| } |
| std::string attribute = property_node.arguments[0].name_or_value; |
| return GetIA2Attribute(target, attribute); |
| } |
| |
| if (property_node.name_or_value == "hasState") { |
| if (!property_node.arguments.size()) { |
| LOG(ERROR) << "Error: " << property_node.name_or_value |
| << "called without argument"; |
| return AXOptionalObject::Error(); |
| } |
| std::string state = property_node.arguments[0].name_or_value; |
| return HasIA2State(target, state); |
| } |
| |
| // Todo: add support for |
| // - [ ] test.getInterface(IAccessible2).get_groupPosition |
| // - [ ] test.getInterface(IAccessible2).get_localizedExtendedRole |
| |
| return AXOptionalObject::Error(); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::InvokeForIA2Hypertext( |
| IA2HypertextComPtr target, |
| const AXPropertyNode& property_node) const { |
| return AXOptionalObject::Error(); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::InvokeForIA2Table( |
| IA2TableComPtr target, |
| const AXPropertyNode& property_node) const { |
| if (property_node.name_or_value == "selectedColumns") { |
| return GetSelectedColumns(target); |
| } |
| |
| return AXOptionalObject::Error(); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::InvokeForIA2TableCell( |
| IA2TableCellComPtr target, |
| const AXPropertyNode& property_node) const { |
| return AXOptionalObject::Error(); |
| |
| // Todo: add support for |
| // - [ ] test.getInterface(IAccessibleTableCell).get_rowIndex |
| // - [ ] test.getInterface(IAccessibleTableCell).get_columnIndex |
| // - [ ] test.getInterface(IAccessibleTableCell).get_rowExtent |
| // - [ ] test.getInterface(IAccessibleTableCell).get_columnExtent |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::InvokeForIA2TextSelectionContainer( |
| IA2TextSelectionContainerComPtr target, |
| const AXPropertyNode& property_node) const { |
| if (property_node.name_or_value == "selections") { |
| return GetSelections(target); |
| } |
| |
| return AXOptionalObject::Error(); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::InvokeForIA2Text( |
| IA2TextComPtr target, |
| const AXPropertyNode& property_node) const { |
| return AXOptionalObject::Error(); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::InvokeForIA2Value( |
| IA2ValueComPtr target, |
| const AXPropertyNode& property_node) const { |
| return AXOptionalObject::Error(); |
| |
| // Todo: add support for |
| // - [ ] test.getInterface(IAccessibleValue).get_currentValue |
| // - [ ] test.getInterface(IAccessibleValue).get_minimumValue |
| // - [ ] test.getInterface(IAccessibleValue).get_maximumValue |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::GetRole( |
| IAccessibleComPtr target) const { |
| base::win::ScopedVariant variant_self(CHILDID_SELF); |
| base::win::ScopedVariant ia_role_variant; |
| if (SUCCEEDED(target->get_accRole(variant_self, ia_role_variant.Receive()))) { |
| return AXOptionalObject(Target(RoleVariantToString(ia_role_variant))); |
| } |
| |
| return AXOptionalObject::Error(); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::GetName( |
| IAccessibleComPtr target) const { |
| base::win::ScopedVariant variant_self(CHILDID_SELF); |
| base::win::ScopedBstr name; |
| auto result = target->get_accName(variant_self, name.Receive()); |
| if (result == S_OK) |
| return AXOptionalObject(Target(base::WideToUTF8(name.Get()))); |
| if (result == S_FALSE) |
| return AXOptionalObject(Target(std::string())); |
| |
| return AXOptionalObject::Error(); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::GetDescription( |
| IAccessibleComPtr target) const { |
| base::win::ScopedVariant variant_self(CHILDID_SELF); |
| base::win::ScopedBstr desc; |
| auto result = target->get_accDescription(variant_self, desc.Receive()); |
| if (result == S_OK) |
| return AXOptionalObject(Target(base::WideToUTF8(desc.Get()))); |
| if (result == S_FALSE) |
| return AXOptionalObject(Target(std::string())); |
| |
| return AXOptionalObject::Error(); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::HasState(IAccessibleComPtr target, |
| std::string state) const { |
| base::win::ScopedVariant variant_self(CHILDID_SELF); |
| int32_t ia_state = 0; |
| base::win::ScopedVariant ia_state_variant; |
| if (target->get_accState(variant_self, ia_state_variant.Receive()) == S_OK && |
| ia_state_variant.type() == VT_I4) { |
| std::vector<std::wstring> state_strings; |
| ia_state = ia_state_variant.ptr()->intVal; |
| IAccessibleStateToStringVector(ia_state, &state_strings); |
| |
| for (const auto& str : state_strings) { |
| if (base::WideToUTF8(str) == state) |
| return AXOptionalObject(Target(true)); |
| } |
| return AXOptionalObject(Target(false)); |
| } |
| |
| return AXOptionalObject::Error(); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::QueryInterface( |
| IAccessibleComPtr target, |
| std::string interface_name) const { |
| DEFINE_IA2_QI_ENTRY(IAccessible2) |
| DEFINE_IA2_QI_ENTRY(IAccessibleHypertext) |
| DEFINE_IA2_QI_ENTRY(IAccessibleTable) |
| DEFINE_IA2_QI_ENTRY(IAccessibleTableCell) |
| DEFINE_IA2_QI_ENTRY(IAccessibleTextSelectionContainer) |
| DEFINE_IA2_QI_ENTRY(IAccessibleText) |
| DEFINE_IA2_QI_ENTRY(IAccessibleValue) |
| |
| return AXOptionalObject::Error("Unsupported " + interface_name + |
| " interface"); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::GetIA2Role(IA2ComPtr target) const { |
| LONG role = 0; |
| if (SUCCEEDED(target->role(&role))) |
| return AXOptionalObject( |
| Target(base::WideToUTF8(IAccessible2RoleToString(role)))); |
| |
| return AXOptionalObject::Error(); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::GetIA2Attribute( |
| IA2ComPtr target, |
| std::string attribute) const { |
| absl::optional<std::string> value = |
| GetIAccessible2Attribute(target, attribute); |
| if (value) |
| return AXOptionalObject(Target(*value)); |
| return AXOptionalObject::Error(); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::HasIA2State( |
| IA2ComPtr target, |
| std::string state) const { |
| AccessibleStates states; |
| if (target->get_states(&states) == S_OK) { |
| std::vector<std::wstring> state_strings; |
| IAccessible2StateToStringVector(states, &state_strings); |
| |
| for (const auto& str : state_strings) { |
| if (base::WideToUTF8(str) == state) |
| return AXOptionalObject(Target(true)); |
| } |
| return AXOptionalObject(Target(false)); |
| } |
| |
| return AXOptionalObject::Error(); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::GetSelectedColumns( |
| const IA2TableComPtr target) const { |
| ScopedCoMemArray<LONG> columns; |
| if (target->get_selectedColumns(INT_MAX, columns.Receive(), |
| columns.ReceiveSize()) == S_OK) { |
| return AXOptionalObject({std::move(columns)}); |
| } |
| return AXOptionalObject::Error(); |
| } |
| |
| AXOptionalObject AXCallStatementInvokerWin::GetSelections( |
| IA2TextSelectionContainerComPtr target) const { |
| ScopedCoMemArray<IA2TextSelection> selections; |
| if (target->get_selections(selections.Receive(), selections.ReceiveSize()) == |
| S_OK) { |
| return AXOptionalObject({std::move(selections)}); |
| } |
| return AXOptionalObject::Error(); |
| } |
| |
| bool AXCallStatementInvokerWin::IsIAccessibleAndNotNull( |
| const Target& target) const { |
| return target.Is<IAccessibleComPtr>() && |
| target.As<IAccessibleComPtr>().Get() != nullptr; |
| } |
| |
| } // namespace ui |